home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The 640 MEG Shareware Studio 2
/
The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO
/
utility
/
clkdev14.zip
/
CLOCKDEV.ASM
< prev
Wrap
Assembly Source File
|
1991-07-24
|
118KB
|
4,228 lines
;**********************************************************************
;* Copyright (C) 1988, 1989, 1990, 1991, Alan P. Barrett, Durban. *
;* *
;* Permission is granted to use, copy or distribute this software in *
;* any way, except for profit. Modified versions of this software *
;* may be distributed, provided that this notice is retained and the *
;* modifications are clearly indicated. *
;* *
;* No liability is accepted for any loss or damage whatsoever caused *
;* or allegedly caused by the use or misuse of this software. *
;**********************************************************************
;***********************************************************************
;* File: CLOCKDEV.ASM
;* Author: A.P. Barrett
;* Date created: 06 May 1988
;* Date edited: 24 Jul 1991
;* Purpose: MS-DOS installable device driver, for CLOCK$ device.
;* On an AT: Makes some DOS date and time accesses use the AT BIOS
;* real time clock. (The AT clock has a 1 second granularity,
;* which is too poor to be used for all time accesses.)
;* On a PC with a National Semiconductors MM58167AN clock chip:
;* Makes all DOS date and time accesses use the chip.
;* On a PC with an MSM6242 clock chip: Makes some DOS date and
;* time accesses use the chip. (The chip has a 1 second
;* granularity.)
;* On a PC with no timer chip, fixes the date change bug present in
;* MSDOS 3.2 (and possibly other DOS versions).
;* Instal this device driver by placing
;* DEVICE=CLOCK.DEV
;* in the CONFIG.SYS file. (Some additional options might be required;
;* see the help message that is printed on startup.)
;* Compile as follows:
;* MASM CLOCKDEV ;
;* LINK CLOCKDEV ;
;* EXE2BIN CLOCKDEV.EXE CLOCK.DEV
;* DEL CLOCKDEV.EXE
;* DEL CLOCKDEV.OBJ
;*
;* Please send comments, bug fixes, complaints etc. to the author:
;* Alan Barrett, Department of Electronic Engineering, University of
;* Natal, Durban, 4001, South Africa.
;* RFC822 email address: barrett@ee.und.ac.za
;***********************************************************************
; Reasons for using CLOCK.DEV:
;
; If you have an AT, you probably have to run the SETUP program
; whenever the date or time needs to be changed. The SETUP
; program's insistance on rebooting the computer has led to the
; availability of several small utilities that set the real time
; clock with DOS's idea of the date and time, so instead of running
; SETUP you can first use the DOS date and time commands, and then
; run some special utility to update the real time clock. Using
; CLOCK.DEV will make the DOS date and time commands set the real
; time clock, without running any other programs.
;
; If you have a PC (or XT) with a clock chip, then you probably
; have a special program to read and set the time. The program may
; be called TIMER, or there may be two programs called SETCLOCK and
; GETCLOCK (or some other combination). You have to use a special
; command to update the time maintained by the card, because the
; DOS date and time commands do not do that. Another problem with
; some of these clock cards is that the year does not change
; correctly. Using CLOCK.DEV will make the DOS date and time
; commands set the real time clock, without running any other
; programs, and will ensure that the year changes correctly at the
; beginning of January.
;
; If you use MS-DOS version 3.2, the DOS date will not change at
; midnight. Using CLOCK.DEV will fix this problem, even on a PC
; without a clock chip. (Some other DOS versions might also have
; this problem.)
;
; If you leave your PC turned on but idle for more than one day (so
; that the time passes midnight more than once), then the date will
; not be correct; the date will move forward by only one day,
; instead of by more than one day. If your PC has a clock chip,
; using CLOCK.DEV will correct this problem. If you do not have a
; clock chip, there is no way of correcting the problem without
; modifying the BIOS.
;
; How to use CLOCK.DEV:
;
; Simply add the line
; DEVICE=\CLOCK.DEV
; to your CONFIG.SYS file. (Include a drive and directory name if
; CLOCK.DEV is not stored in the root directory.) When your
; computer is booted up, it will determine whether you have an AT
; or an add-on clock chip or neither, and will act appropriately.
; Although the built in default action will usually be suitable,
; there are some options that can be specified in the DEVICE=...
; line; try using "DEVICE=CLOCK.DEV -H" to get a list of options,
; or just look for the message in the source code.
;***********************************************************************
; Modification history:
;
; Ver 1.2 28 May 1988
; First release. Supports AT BIOS clock, supports add-on
; National Semiconductor MM58167AN clock chip for XTs,
; fixes MS-DOS version 3.2 date change bug.
; 15 Jun 1988
; Fixed bug in conversion from date (year, month and day)
; to day number (since 01-Jan-1980). Comment said "BH is
; still zero", but of course it wasn't.
; 07 Oct 1988
; Improved error reporting when there is no clock chip.
; There are now two separate messages, depending on
; whether user said "-C+" or"-C=xxxx".
; 20 Jun 1989
; When time is first established at bootup, and later when
; time is changed, be sure to set plain BIOS time,
; even if a clock chip is used. This is desirable because
; some software uses the plain BIOS time instead of the
; DOS time. (Plain BIOS time is INT 1Ah function 0 and 1.
; It is not to be confused with AT BIOS time, which is
; INT 1Ah function 2, 3, 4 and 5.)
; 20 Mar 1990
; When the date changes, the BIOS will report the date change
; to the first program that asks for the plain BIOS time,
; but that might be another program, other than this one.
; In such a case, the fact that the date has changed will be
; hidden from us. But we can partially fix the problem by
; checking if the BIOS time ever seems to run backwards,
; and assuming a date change if that does happen.
;
; Ver 1.3 04 Sep 1990
; Added support for MSM6242 clock chip. Changed command line
; options slightly. Restructured some code to make
; support for other clock chips a little easier to add.
; (Memory usage increased from 1488 to 1680 bytes.)
; 05 Sep 1990
; MSM6242 returns junk in high 4 bits of each port, so ignore
; that.
;
; Ver 1.4 24 Jul 1991
; Slight cleanup. No new functionality.
;***********************************************************************
; Discussion:
;
; On the IBM PC, the 8253 timer divides a 1.19318 Mhz clock by 65536 to
; generate a time-base interrupt on IRQ0, which corresponds to 8088
; hardware interrupt number 8. The BIOS uses this interrupt to increment
; a 32-bit counter, in order to keep track of the time-of-day. The BIOS
; also uses the time-base interrupt to handle diskette motor timeouts,
; and performs an INT 1Ch to allow user written routines to be invoked
; once for each time-base interrupt.
;
; When the BIOS time-of-day count reaches 1573040, it is reset to zero
; and an overflow flag is set. The next time the BIOS time-of-day
; service (interrupt 1Ah function 0) is called, the overflow flag is
; reset, and is returned to the caller. This is a signal to the caller
; of the BIOS time-of-day service that the date has changed.
;
; If the date changes more than once before the BIOS time-of-day service
; is called, there is no way for the caller to know this. Thus if a
; machine is left idle for several days, the date will change only once.
; This behaviour is unpleasant, but cannot be changed without modifying
; the BIOS or intercepting either the time-base interrupt or the user
; tick interrupt.
;
; MS-DOS interrupt 21h functions 2Ah, 2Bh, 2Ch and 2Dh are used to get
; and set the date and time. MS-DOS implements these functions via a
; device driver whose attributes indicate that it is a clock device.
; This device is usually named CLOCK$. Reading and writing the clock
; device gets and sets the date and time. The clock device presumably
; relies on the BIOS time-of-day service for the time, and maintains its
; own record of the current date. When the BIOS indicates that the date
; has changed, the DOS updates its idea of the current date.
;
; Versions of MS-DOS up to 3.1 behaved as just described, but MS-DOS
; version 3.2 does not ever increment the current date. This behaviour
; is entirely unacceptable, because at midnight the time changes but the
; date does not. If the date stamps on files are used to keep track of
; which files are newer than others, then files modified just after
; midnight will appear to be OLDER than files modified just before
; midnight.
;
; The IBM-PC AT (and compatibles) contains a built-in real time clock,
; which is automatically read when the machine is booted up. The AT BIOS
; provides functions to read and set the real time clock. (BIOS interrupt
; 1Ah, functions 2, 3, 4 and 5.) Changing the time in the real time clock
; is usually done only by the SETUP program. The clock has a resolution
; of 1 second.
;
; Many users instal peripheral cards containing real time clock chips
; that continue to function when the power is turned off. These cards
; are usually supplied together with programs to change their idea of
; the date and time, and to make the DOS idea of the date and time
; match the card's idea of the date and time. The clock chip is usually
; consulted only when the computer is booted up. A problem with many
; clock chips (or rather, with the programs supplied to control the
; chips) is that the year may not change correctly.
;
; This installable device driver is a replacement for the DOS clock
; device, and is an attempt to solve the following problems:
;
; The DOS date and time must never go backwards. When the BIOS
; indicates that the date has changed, THE DATE MUST CHANGE.
; (The MS-DOS 3.2 date change bug is fixed by this program.)
;
; The real time clock maintained by the BIOS on an AT computer
; (and usually accessible only via the SETUP program) should
; be used for all DOS date and time functions.
;
; The AT BIOS clock has a one-second resolution. This is
; improved by usually reading the ordinary BIOS timer, and only
; using the AT BIOS clock when the date changes or after a long
; idle period, or when the user changes the date or time.
;
; If there is a real time clock on a peripheral card, the DOS
; date and time should match that on the peripheral card.
;
; If there is a real time clock on a peripheral card, the year
; should change correctly. The software provided with some
; such add-on clock chips doesn't work properly.
;
; The date should change the correct number of times if the
; computer is left unattended for several days.
; (Not fixed yet, for machines without clock chips.)
;
;***********************************************************************
;
; These definitions control the conditional assembly
;
False = 0
True = not False
;
; Make do_debug_writes True to get lots of additional output that
; may assist in debugging. The macros that actually perform the
; debugging output are disabled if do_debug_writes is False.
;
do_debug_writes = False
;
; Make add_test_code True to include additional code for testing.
;
add_test_code = False
;
; It may be necessary to switch to a local stack. This is because
; DOS does not guarantee to have more than 40 free words of stack
; space when the device driver is called. Even the 40 words mentioned
; in the Programmer's Reference is probably not guaranteed.
; This driver uses about 30 words of stack space if the debug output
; is disabled, so use_local_stack can be set to False. ????
; When debugging output is performed, much more stack space is used,
; so use_local_stack should be True, with a reasonably large
; local_stack_size.
; Note that the size of the local stack is specified in BYTES.
; Remember that any interrupts occuring while this program is busy
; will need space on the stack.
;
if do_debug_writes
use_local_stack = True ; Strongly recommend this be set to TRUE
local_stack_size = 300
else
use_local_stack = True ; Try TRUE if FALSE gives problems
local_stack_size = 160
endif
;***********************************************************************
;
; Define the order of segments in memory
;
; All the segments go together in a GROUP, so the whole thing will
; actually be just one 64k segment.
;
; The reason for defining multiple segments here is so that the source code
; can be written in any order, and the linker will place everything in the
; correct order in the executable file. We particularly want to ensure
; that non-resident code and data appears after the resident code and data,
; so that its space can be freed after initialisation. We must also
; ensure that the header comes first in memory.
;
header_segment segment byte
header_segment ends
cgroup group header_segment
resident_data segment byte
resident_data ends
cgroup group resident_data
resident_code segment byte
resident_code ends
cgroup group resident_code
if use_local_stack
local_stack_seg segment word
;;; db '-->'
db (local_stack_size+7)/8 dup (' STACK ')
even ; Ensure word alignment
local_stack_top label word
;;; db '<--'
local_stack_seg ends
cgroup group local_stack_seg
endif ; use_local_stack
startup_data segment byte
end_resident_memory label byte ; This must be the start of the section
; that is used only at startup time
startup_data ends
cgroup group startup_data
startup_code segment byte
startup_code ends
cgroup group startup_code
identity_seg segment byte
identity_seg ends
cgroup group identity_seg
;
; CS will point to the CGROUP segment while the code is running.
;
assume cs:cgroup
;***********************************************************************
;
; This is the message printed at initialisation. We arrange for the
; message to be the last thing in the executable file so that it
; is easy to find.
;
identity_seg segment
start_message label byte
db 'CLOCKDEV version 1.4 [24 Jul 1991]. Copyright (C) A.P. Barrett',13,10
start_message_len equ $-start_message
identity_seg ends
;***********************************************************************
;
; Some useful macro definitions
;
;
; Push all registers except CS, IP, SS and SP.
;
save_regs macro
pushf
push ax
push bx
push cx
push dx
push es
push ds
push si
push di
push bp
endm
;
; Pop all registers saved by the save_regs macro
;
restore_regs macro
pop bp
pop di
pop si
pop ds
pop es
pop dx
pop cx
pop bx
pop ax
popf
endm
;
; JCXNZ: jump if CX is non zero.
;
jcxnz macro dest
local l1
jcxz l1
jmp short dest
l1:
endm
;
; Conditional jump, where the target is too far away.
; Use, for example, <jcond je, dest> instead of <je dest>.
;
; There must be a better way of doing this ???
;
jcond macro type,dest
local next, l1
ifidni <type>, <jcxz> ; CX register zero
jcxz l1
jmp short next
endif
ifidni <type>, <jcxnz> ; CX register non zero (really a macro)
jcxz next
endif
ifidni <type>, <jc> ; carry flag set
jnc next
endif
ifidni <type>, <jnc> ; carry flag clear
jc next
endif
ifidni <type>, <jz> ; zero flag set
jnz next
endif
ifidni <type>, <jnz> ; zero flag clear
jz next
endif
ifidni <type>, <jo> ; overflow flag set
jno next
endif
ifidni <type>, <jno> ; overflow flag clear
jo next
endif
ifidni <type>, <js> ; sign flag set (negative)
jns next
endif
ifidni <type>, <jns> ; sign flag clear (positive)
js next
endif
ifidni <type>, <jp> ; parity flag set (even parity)
jnp next
endif
ifidni <type>, <jnp> ; parity flag clear (odd parity)
jp next
endif
ifidni <type>, <jpe> ; even parity
jpo next
endif
ifidni <type>, <jpo> ; odd parity
jpe next
endif
ifidni <type>, <je> ; equal
jne next
endif
ifidni <type>, <jl> ; less (signed)
jnl next
endif
ifidni <type>, <jg> ; greater (signed)
jng next
endif
ifidni <type>, <jb> ; below (unsigned)
jnb next
endif
ifidni <type>, <ja> ; above (unsigned)
jna next
endif
ifidni <type>, <jle> ; less or equal (signed)
jnle next
endif
ifidni <type>, <jge> ; greater or equal (signed)
jnge next
endif
ifidni <type>, <jbe> ; below or equal (unsigned)
jnbe next
endif
ifidni <type>, <jae> ; above or equal (unsigned)
jnae next
endif
ifidni <type>, <jne> ; not equal
je next
endif
ifidni <type>, <jnl> ; not less (signed)
jl next
endif
ifidni <type>, <jng> ; not greater (signed)
jg next
endif
ifidni <type>, <jnb> ; not below (unsigned)
jb next
endif
ifidni <type>, <jna> ; not above (unsigned)
ja next
endif
ifidni <type>, <jnle> ; not less or equal (signed)
jle next
endif
ifidni <type>, <jnge> ; not greater or equal (signed)
jge next
endif
ifidni <type>, <jnbe> ; not below or equal (unsigned)
jbe next
endif
ifidni <type>, <jnae> ; not above or equal (unsigned)
jae next
endif
l1: jmp dest
next:
endm
;
; If debugging is disabled, the following macros are defined as do-nothing.
; If debugging is enabled, they are defined in the normal way.
;
if do_debug_writes
;
; Display a message for debugging
;
debug_message macro msg
local m, m_len
; The message must appear in memory somewhere
resident_data segment
m db msg
m_len equ $-m
resident_data ends
; Save registers
push cx
push si
push ds
; Print the message
push cs
pop ds
mov si,offset cgroup:m
mov cx,m_len
call display_message
; Restore registers
pop ds
pop si
pop cx
endm
;
; Display a character (for debugging)
;
debug_show_char macro chr
; Save regs
pushf
push ax
push bx
; Display
mov ah,0Eh
mov al,chr
mov bx,1
int 10h
; Restore regs
pop bx
pop ax
popf
endm
;
; Display a byte in hexadecimal (for debugging)
;
debug_hex_byte macro byt
push ax
mov al,byt
call display_hex_byte
pop ax
endm
;
; Display a string of hex bytes, starting at seg:addr (for debugging)
;
debug_hex_string macro seg,addr,count,count_size
push cx
push si
push ds
push seg
pop ds
lea si,addr
ifidni <count_size>, <byte>
mov cl,count
xor ch,ch
else
mov cx,count
endif
call display_hex_bytes
pop ds
pop si
pop cx
endm
;
; Display a word in hexadecimal (for debugging)
;
debug_hex_word macro wrd
push ax
mov ax,wrd
xchg al,ah
call display_hex_byte
mov al,ah
call display_hex_byte
pop ax
endm
;
; Display a DWORD in hexadecimal. THE VALUE MUST BE IN CX_DX. For debugging.
;
debug_hex_cx_dx macro
push ax
mov al,ch
call display_hex_byte
mov al,cl
call display_hex_byte
mov al,dh
call display_hex_byte
mov al,dl
call display_hex_byte
pop ax
endm
else ; do_debug_writes
;
; Empty definitions for the debug macros, if debugging is turned off.
;
debug_message macro msg
endm
debug_show_char macro chr
endm
debug_hex_byte macro byt
endm
debug_hex_string macro seg,addr,count,count_size
endm
debug_hex_word macro wrd
endm
debug_hex_cx_dx macro
endm
endif ; do_debug_writes
;***********************************************************************
;
; Equates for DOS device driver attributes
;
D_ATT_chr equ 1000000000000000b ; Character device
D_ATT_blk equ 0000000000000000b ; Block device (chr bit is off)
D_ATT_ioc equ 0100000000000000b ; Supports IOCTL
D_ATT_oub equ 0010000000000000b ; Output until busy (char only)
D_ATT_fat equ 0010000000000000b ; Uses FATID byte (block only)
D_ATT_opn equ 0000100000000000b ; Understands Open/Close
D_ATT_3_2 equ 0000000001000000b ; Supports DOS 3.2 functions
D_ATT_clk equ 0000000000001000b ; This is the clock device
D_ATT_nul equ 0000000000000100b ; This is the NUL device
D_ATT_sto equ 0000000000000010b ; This is the std. output device
D_ATT_sti equ 0000000000000001b ; This is the std. input device
;
; Equates for offsets of various items within driver requests
;
; Items in the request header
D_REQ_len equ 0 ; Length of the request block
D_REQ_unit equ 1 ; Device subunit number
D_REQ_command equ 2 ; Command code number
D_REQ_status equ 3 ; Returned status code
; Additional items in the INIT request
D_INIT_end equ 14 ; Points to end of resident part
D_INIT_args equ 18 ; Points to command line arguments
; Additional items in read, write and IOCTL requests
D_RDWR_media equ 13 ; Media byte from BPB
D_RDWR_buffer equ 14 ; Address of transfer buffer
D_RDWR_length equ 18 ; Number of chars or sectors
D_RDWR_startsec equ 20 ; Start sector num. (block device)
; Additional item in the non-destructive read request
D_PEEK_char equ 13 ; Returned character
;
; Equates for request completion status codes.
; These are all byte values, to be stored in the high byte of the
; status word.
;
D_STAT_error equ 10000000b ; Error
D_STAT_busy equ 00000010b ; Device busy
D_STAT_done equ 00000001b ; Request complete
;
; Equates for request error codes.
; These are to be stored in the low byte of the status word when the
; ERROR bit is set in the high byte.
;
D_ERR_write_prot equ 0 ; Write protect violation
D_ERR_bad_unit equ 1 ; Unknown unit number
D_ERR_not_ready equ 2 ; Device not ready
D_ERR_bad_command equ 3 ; Unknown command
D_ERR_CRC equ 4 ; CRC error
D_ERR_bad_length equ 5 ; Bad request structure length
D_ERR_seek equ 6 ; Seek error
D_ERR_unknown_media equ 7 ; Unknown media
D_ERR_sector equ 8 ; Sector not found
D_ERR_paper equ 9 ; Printer out of paper
D_ERR_write equ 0Ah ; Write fault
D_ERR_read equ 0Bh ; Read fault
D_ERR_general equ 0Ch ; General failure
D_ERR_bad_change equ 0Fh ; Invalid disk change
;***********************************************************************
;
; CODE STARTS HERE
;
header_segment segment
assume ds:nothing, es:nothing
;
; First, we need a special header so that DOS recognises the driver correctly.
;
CLK_header label byte
dw -1,-1 ; List linkage filled in when DOS
; installs the device driver
dw D_ATT_chr+D_ATT_clk ; Attribute word
dw offset cgroup:CLK_strategy ; STRATEGY entry point
dw offset cgroup:CLK_interrupt ; "INTERRUPT" entry point
db 'CLOCK$ ' ; Name, must be 8 chars
;
; For debugging only: Something to search for in memory to help find
; where the driver has been loaded.
; When not debugging, comment this out to save a few bytes of memory.
;
;;; db 'APB'
header_segment ends
;***********************************************************************
;
; Data used by the resident portion of the CLOCK$ device driver.
;
resident_data segment
;
; Pointer to the header of the current device request.
;
request_pointer dd ?
if use_local_stack
;
; Saved values needed for switching to a local stack
;
saved_ax dw ?
saved_ss dw ?
saved_sp dw ?
endif ; use_local_stack
;
; The current date and time.
; This is stored in the format required by the DOS, so a READ or WRITE
; is accomplished by copying the six-byte data block either to or from here.
;
; The order of the following data definitions is important.
;
CLK_date_time_len equ 6 ; Length of the date and time data
CLK_date_time label byte
CLK_date_days dw 0 ; Days since 01-Jan-1980
CLK_time_min db 0 ; Current time: minutes
CLK_time_hour db 0 ; Current time: hour
CLK_time_hsec db 0 ; Current time: hundredths of secs
CLK_time_sec db 0 ; Current time: seconds
;
; Some more date values, used internally by various procedures.
;
CLK_date_century db 0 ; First two digits of year
CLK_date_year db 0 ; Last two digits of year
CLK_date_month db 0 ; Current month
CLK_date_day db 0 ; Day of month
CLK_date_weekday db 0 ; Day of week (0 = Sunday)
;
; This command table is used by the CLOCK$ device "interrupt" handler.
; Each word is a pointer to a routine that handles a particular function.
;
CLK_command_table label byte
dw offset cgroup:CLK_init ; 0: Instal the device driver
dw offset cgroup:bad_command ; 1: Media check (block dev.)
dw offset cgroup:bad_command ; 2: Build BPB (block dev.)
dw offset cgroup:bad_command ; 3: IOCTL input
dw offset cgroup:CLK_input ; 4: Input (get date, time)
dw offset cgroup:CLK_peek ; 5: Non destr. read no wait
dw offset cgroup:exit_ok ; 6: Input status query
dw offset cgroup:exit_ok ; 7: Input flush
dw offset cgroup:CLK_output ; 8: Output (set date, time)
dw offset cgroup:CLK_output ; 9: Output with verify
dw offset cgroup:exit_ok ;10: Output status query
dw offset cgroup:exit_ok ;11: Output flush
CLK_last_command equ 11
; all other function codes are treated as bad commands
;
; The timer chip base address is determined during initialisation.
;
CLK_chip_IO_base dw 0FFFFh ; I/O base address for clock chip.
; Default value FFFF means look in all usual places.
; Value zero means do not use chip.
; Other value is place to look.
;
; This pointer specifies a subroutine to be called by the
; CLK_fix_our_time procedure. The pointer is set by the
; startup code.
;
CLK_get_procedure_ptr dw ?
;
; This pointer specifies a subroutine to be called by the
; CLK_set_other_timers procedure. The pointer is set by the
; startup code.
;
CLK_set_procedure_ptr dw ?
;
; When a coarse grain clock chip is used, CLK_get_procedure_ptr
; will point to CLK_get_BIOS_or_other_time, and this pointer
; will point to a subroutine that will get the time from the
; 'other' source. This is set by the startup code.
;
CLK_get_other_procedure_ptr dw ?
;
; These words are used for temporary storage of intermediate results.
;
t1 dw ?
t2 dw ?
t3 dw ?
t4 dw ?
t5 dw ?
resident_data ends
;***********************************************************************
;
; A null procedure. May be pointed to by CLK_set_procedure_ptr.
;
resident_code segment
null_proc proc near
ret
null_proc endp
resident_code ends
;***********************************************************************
;
; The strategy entry point is called by DOS to pass the request block.
; The driver simply saves the address of the request block and returns.
; On multi-tasking systems, the driver should add the request to a queue.
;
resident_code segment
CLK_strategy proc far
;
; ES:BX points to the request block. Just save the address and return
;
mov word ptr cs:[request_pointer],bx
mov word ptr cs:[request_pointer+2],es
ret ; FAR return
CLK_strategy endp
resident_code ends
;***********************************************************************
;
; The interrupt entry point handles the request that was saved by the
; strategy entry point. DOS calls it immediately after the strategy
; routine returns.
;
resident_code segment
CLK_interrupt proc far
if use_local_stack
;
; Switch to a local stack, in case the stack is too small.
;
mov cs:[saved_ax],ax
mov ax,sp
mov cs:[saved_sp],ax
mov ax,ss
mov cs:[saved_ss],ax
mov ax,cs
mov ss,ax
mov sp,offset cgroup:local_stack_top
mov ax,cs:[saved_ax]
endif ; use_local_stack
;
; Save registers
;
save_regs
;
; Make DS:BX point to the request header
;
lds bx,cs:[request_pointer]
debug_message '{ request header is at '
debug_hex_word ds
debug_show_char ':'
debug_hex_word bx
debug_message ' data is '
debug_hex_string ds,[bx],<byte ptr ds:[bx]>,byte
debug_message <'}',13,10>
;
; Get the command code, jump to error handler for bad command,
; look up the command in the table
;
mov al,ds:[bx].D_REQ_command ; Get the command code
cmp al,CLK_last_command ; See if it is too large
jcond jg, bad_command ; Error if bad code
cbw ; Sign extend AL to AX
mov si,offset cgroup:CLK_command_table
add si,ax
add si,ax ; Now CS:SI points to the entry
; in the command table
;
; Now jump to the appropriate handler subroutine.
;
jmp word ptr cs:[si] ; jump to the appropriate routine
CLK_interrupt endp
resident_code ends
;***********************************************************************
;
; Routines for various operations required by the CLOCK$ "interrupt"
; handler. One of these routines is called with DS:BX pointing to the
; request header, and with the command code in AL.
;
resident_code segment
;
; Request code 4: Input
;
CLK_input proc near
debug_message '{ CLOCK$ input '
;
; Make sure our time is correct.
;
push bx
call CLK_fix_our_time ; Get the new time value
pop bx
;
; Get the requested transfer length from the request header.
; This is the size of the callers buffer, so make it smaller
; if necessary, because we will never return more than CLK_date_time_len
; bytes. If count is changed from the requested value, inform the caller.
;
mov cx,word ptr ds:[bx].D_RDWR_length ; Requested length
debug_message '( request length '
debug_hex_word cx
debug_message ' ) '
cmp cx,CLK_date_time_len ; Check if CX is too large
jbe CLK_inp_len_ok ; Skip ahead if count OK
mov cx,CLK_date_time_len ; Set CX to max size
mov word ptr ds:[bx].D_RDWR_length,cx ; Return true length
CLK_inp_len_ok:
jcond jcxz, exit_ok ; Exit if there is nothing to do
;
; Get the address of the caller's buffer, from the request header.
; Copy data from the current date and time buffer to the caller's buffer.
;
les di,dword ptr ds:[bx].D_RDWR_buffer ; Destination address
mov si,offset cgroup:CLK_date_time ; Make DS:SI point to source
push cs
pop ds
cld ; Make sure the move goes forwards
debug_message '( move from DS:SI = '
debug_hex_word ds
debug_show_char ':'
debug_hex_word si
debug_message ' to ES:DI = '
debug_hex_word es
debug_show_char ':'
debug_hex_word di
debug_message ' length '
debug_hex_word cx
debug_message ' ) '
debug_message '{ data: '
debug_hex_string ds,[si],cx,word
debug_message '} '
rep movsb ; Do the data transfer
jmp exit_ok
CLK_input endp
;
; Request code 5: Non destructive read, no wait
;
CLK_peek proc near
debug_message '{ CLOCK$ peek '
;
; Make sure our time is correct.
;
push bx
call CLK_fix_our_time ; Get the new time value
pop bx
;
; Return first byte from the current date buffer
;
mov al,byte ptr cs:[CLK_date_time] ; First byte of day
mov ds:[bx].D_PEEK_char,al ; Return the byte
jmp exit_ok ; Finished successfully
CLK_peek endp
;
; Request code 8: Output
; Request code 9: Output with verify
;
CLK_output proc near
debug_message '{ CLOCK$ output '
;
; Get the requested transfer length
;
mov cx,word ptr ds:[bx].D_RDWR_length ; Length of write
;
; CX contains the size of the callers buffer. If it is too small, then
; ignore the write request. If it is too large, perform the write as
; usual, but ignore any extra data in the buffer. In either case, let the
; caller believe that the write was successful.
;
debug_message '( request length '
debug_hex_word cx
debug_message ' ) '
cmp cx,CLK_date_time_len ; Check if length is OK
jcond jb, bad_length ; Error if length is too small
mov cx,CLK_date_time_len ; Set CX to max size
;
; Get address of caller's data buffer from the request header.
; Make sure the caller's data is valid
;
lds si,dword ptr ds:[bx].D_RDWR_buffer ; Source data address
call CLK_check_data_validity ; Check for bad date or time
jcond jc, general_failure ; Return error code
;
; Copy data from the caller's buffer to the current date and time buffer.
;
push cs ; Make ES:DI point to destination
pop es ;
mov di,offset cgroup:CLK_date_time ;
cld ; Make sure the move goes forwards
debug_message '( move from DS:SI = '
debug_hex_word ds
debug_show_char ':'
debug_hex_word si
debug_message ' to ES:DI = '
debug_hex_word es
debug_show_char ':'
debug_hex_word di
debug_message ' length '
debug_hex_word cx
debug_message ' ) '
debug_message '{ data: '
debug_hex_string ds,[si],cx,word
debug_message '} '
rep movsb ; Do the data transfer
;
; Make sure other timers get told about the new time.
;
call CLK_set_other_timers
jmp short exit_ok
CLK_output endp
resident_code ends
;***********************************************************************
;
; Device "interrupt" handlers jump to one of these labels when they have
; finished. If there is more than one device driver in the same source
; file, they can all share these routines.
;
resident_code segment
exit_proc proc far
;
; Here for successful completion
;
exit_ok:
debug_message '(ok) '
mov ax,D_STAT_done shl 8 ; Request completed
if do_debug_writes
jmp exit_save_status
else
jmp short exit_save_status
endif
;
; Here for bad command
;
bad_command:
debug_message '(bad command) '
mov al,D_ERR_bad_command
jmp short exit_error
;
; Here for bad length
;
bad_length:
debug_message '(bad length) '
mov al,D_ERR_bad_length
jmp short exit_error
;
; Here for general failure
;
general_failure:
debug_message '(invalid data) '
mov al,D_ERR_general
;
; Here to return error code
;
exit_error:
mov ah,D_STAT_error+D_STAT_done ; Completed with error
;
; Here to save the status code that is already in AH (and error code in AL)
;
exit_save_status:
;
; Make DS:BX point to the request header, then save the status word
;
lds bx,cs:[request_pointer]
mov word ptr [bx].D_REQ_status,ax
;
; Restore registers and exit
;
debug_message <'}',13,10>
restore_regs
if use_local_stack
;
; Switch back to the normal stack.
;
mov cs:[saved_ax],ax
mov ax,cs:[saved_ss]
mov ss,ax
mov sp,cs:[saved_sp]
mov ax,cs:[saved_ax]
endif ; use_local_stack
ret ; FAR return
exit_proc endp
resident_code ends
;***********************************************************************
;
; Convert dates between various formats.
;
resident_data segment
;
; This table contains the cumulative days per month,
; not allowing for leap years.
;
CLK_month_length_table label word
dw 0, 31, 59, 90, 120, 151, 181 ; 0, Jan, Jan+Feb, ...
dw 212, 243, 273, 304, 334, 365 ; ..., Jan+Feb+...+Dec
resident_data ends
resident_code segment
;
; Convert from years, months, days
; to number of days since 1-Jan-1980
;
CLK_conv_ccyymmdd_days proc near
assume ds:cgroup
debug_message '{ conv_ccyymmdd_days '
;
; Find number of years since 1980. Treat 1980 as year number 0.
; This should never be more than 100, if we assume that the
; IBM-PC will be obsolete by the year 2079.
;
mov al,[CLK_date_century]
debug_message '( century '
debug_hex_byte al
debug_message ' ) '
mov bx,100
mul bl
mov bl,[CLK_date_year]
debug_message '( year '
debug_hex_byte bl
debug_message ' ) '
add ax,bx
sub ax,1980
debug_message '( years since 1980 '
debug_hex_word ax
debug_message ' ) '
mov cx,ax ; Save number of years for later
;
; Convert number of years to number of days.
;
call CLK_conv_years_days
debug_message '( days from years '
debug_hex_word ax
debug_message ' ) '
;
; Add the number of days for completed months this year.
; This is found from a lookup table.
;
mov bl,[CLK_date_month]
debug_message '( month '
debug_hex_byte bl
debug_message ' ) '
xor bh,bh
shl bx,1 ; Each entry is two bytes
mov bx,word ptr ds:[CLK_month_length_table][bx-2]
debug_message '( days from months '
debug_hex_word bx
debug_message ' ) '
add ax,bx
;
; If it March or later, and if this year is a leap year, add another day.
;
test [CLK_date_year],3 ; Is the year a multiple of 4 ?
jnz CLK_conv_ccyymmdd_not_leap
cmp [CLK_date_month],3 ; Is it March or later ?
jb CLK_conv_ccyymmdd_not_leap
inc ax ; Add another day
debug_message '( Add 1 for leap year March or later ) '
CLK_conv_ccyymmdd_not_leap:
;
; Add the current day of the month, less 1.
;
mov bl,[CLK_date_day]
debug_message '( day of month '
debug_hex_byte bl
debug_message ' ) '
dec bl
xor bh,bh
add ax,bx
debug_message '( total days '
debug_hex_word ax
debug_message ' ) '
;
; Store the result.
;
mov [CLK_date_days],ax
debug_message '} '
ret
CLK_conv_ccyymmdd_days endp
;
; Convert from number of days since 1-Jan-1980
; to years, months, days.
;
CLK_conv_days_ccyymmdd proc near
debug_message '{ conv_days_ccyymmdd '
;
; Divide the day number by 365.25 to get number of years.
; There is no need to worry about the century years not being leap years,
; because this software was not running in 1900; 2000 will be a leap year;
; and it is unlikely that this software will be running in the year 2100.
;
; This division is done by multiplying by 4 and dividing by 1461. The
; intermediate result will be longer than 16 bits, but that poses no problems.
;
mov ax,[CLK_date_days]
debug_message '( days '
debug_hex_word ax
debug_message ' ) '
mov bx,4
mul bx
mov bx,1461
div bx
debug_message '( divide by 365.25 gives '
debug_hex_word ax
debug_message ' years since 1980 ) '
;
; Now the number of years is in AX. The additional days (remainder)
; in DX may be incorrect.
;
; Add 1980 and convert to year and century values.
;
mov cx,ax ; Save for later
add ax,1980
mov bl,100
div bl
mov [CLK_date_century],al
mov [CLK_date_year],ah
debug_message '( century '
debug_hex_byte al
debug_message ' ) ( year '
debug_hex_byte ah
debug_message ' ) '
;
; Find the number of days accounted for by the number of full years.
; Subtract that from the total days to get number of days this year.
; Add 1 to make the first of January day number 1.
;
mov ax,cx
call CLK_conv_years_days
debug_message '( days from full years '
debug_hex_word ax
debug_message ' ) '
mov bx,[CLK_date_days]
xchg ax,bx
sub ax,bx
inc ax
debug_message '( day within year '
debug_hex_word ax
debug_message ' ) '
;
; If it is a leap year and the day-within-year is 60, then it must be 29-Feb.
; If a leap year and the day is above 60, subtact one from the day number.
; This will allow the month to be found by searching through the cumulative
; month length table.
;
mov cl,[CLK_date_year]
test cl,3 ; Is it a multiple of 4 ?
jnz CLK_conv_days_not_leap
cmp ax,60 ; Is is 29-Feb ?
jb CLK_conv_days_not_leap ; Actually, it is a leap year,
; but it is still Jan or Feb
jne CLK_conv_days_leap
debug_message '( leap year 29-Feb ) '
mov [CLK_date_month],2 ; It is 29-Feb
mov [CLK_date_day],29
jmp CLK_conv_days_end
CLK_conv_days_leap:
debug_message '( subtract 1 for leap year March or later ) '
dec ax ; Subtract a day, because it is
; March or later in a leap year
CLK_conv_days_not_leap:
;
; Find the month by searching through the cumulative month length
; table for an entry larger than or equal to the day number in AX.
; (AX=1 for 1-Jan.)
;
mov bx,2
CLK_conv_days_month_loop:
cmp ax,word ptr ds:[CLK_month_length_table][bx]
jbe CLK_conv_days_end_month_loop
add bx,2
jmp CLK_conv_days_month_loop
CLK_conv_days_end_month_loop:
mov cx,bx
shr cx,1
debug_message '( month from lookup '
debug_hex_word cx
debug_message ' ) '
mov [CLK_date_month],cl
;
; Find the day within the month.
;
sub ax,word ptr ds:[CLK_month_length_table][bx-2]
mov [CLK_date_day],al
debug_message '( day within month '
debug_hex_word ax
debug_message ' ) '
CLK_conv_days_end:
debug_message '} '
ret
CLK_conv_days_ccyymmdd endp
;
; Convert a number of years since 1980 to a number of days.
; 1980 is year number 0.
; Input years passed in AL, resulting days in AX.
;
CLK_conv_years_days proc near
;
; Multiply number of years by 365.
; This result will be easily representable in 16 bits.
;
mov cx,ax ; Save years for later
mov bx,365
mul bx
;
; Add one day for every fourth year (leap year).
;
; Because we are dealing only with the period from 1980 to 2079,
; we do not have to adjust the number of leap years.
; The year 2000 is a leap year, although 1900, 2100, 2200 and 2300 are not.
;
add cx,3 ; CX := (years + 3)/4
shr cx,1 ;
shr cx,1 ;
add ax,cx ; Add to total days
ret
CLK_conv_years_days endp
;
; Convert from number of days since 1-Jan-1980
; to day of the week. (Sunday = 0.)
;
CLK_conv_days_weekday proc near
debug_message '{ conv_days_weekday '
;
; The first of Jan 1980 (day number 0) was a Tuesday (weekday number 2).
; To convert a day number to a day of the week, we first add 2,
; then find the remainder when divided by 7.
;
mov ax,[CLK_date_days] ; Add two
debug_message '( days '
debug_hex_word ax
debug_message ' ) '
add ax,2
xor dx,dx ; 32-bit divide by 7
mov bx,7
div bx
debug_message '( weekday '
debug_hex_word dx
debug_message ' ) '
mov [CLK_date_weekday],dl
debug_message '} '
ret
CLK_conv_days_weekday endp
resident_code ends
;***********************************************************************
;
; Binary <--> BCD conversions.
;
resident_code segment
;
; Convert binary number in AL to BCD (still in AL).
;
conv_binary_BCD proc near
;
; The AAM instruction does most of the work.
; It places the high BCD digit in AH and the low BCD digit in AL.
;
aam
;
; Shift everything into place.
; (The undocumented {AAD 16} instruction would do this in one step.)
;
push cx
mov cl,4 ; Shift AH value to high nybble
shl ah,cl
or al,ah ; Merge the nybbles into AL
pop cx
ret
conv_binary_BCD endp
;
; Convert BCD number in AL to binary (still in AL).
;
conv_BCD_binary proc near
;
; Shift the high BCD digit into AH.
; (The undocumented {AAM 16} would do this in one step.)
;
push cx
xor ah,ah ; Shift high nybble to AH
mov cl,4
shl ax,cl
shr al,cl ; Low nybble back into position
pop cx
;
; The AAD instruction does the rest of the work.
; It gets the high BCD digit from AH and the low BCD digit from AL,
; and places the binary result into AL.
;
aad
ret
conv_BCD_binary endp
resident_code ends
;
; Check that the value in AL is a valid BCD number.
; Destroys AH in the process, but returns with AL unmodified.
; Returns with carry flag set if not a valid BCD number.
;
startup_code segment
CLK_check_BCD proc near
mov ah,al ; Keep AL unchanged
and ah,0Fh ; Check low nybble
cmp ah,0Ah ; Must be 9 or less
jae CLK_check_BCD_end ; Carry flag will be off if
; jump is taken
mov ah,al ; Check high nybble
and ah,0F0h ;
cmp ah,0A0h ; Must be 90h or less
; Carry flag will be on if OK
CLK_check_BCD_end:
;
; Now complement the carry flag and return.
; The carry flag will then be set if either test failed.
;
cmc
ret
CLK_check_BCD endp
startup_code ends
;***********************************************************************
;
; Some subroutines to read and set timers external to this device driver.
; This includes the BIOS timer, the AT BIOS real time clock, and battery
; backed-up timers on peripheral cards.
; The routines here simply call other routines, choosing which others
; to call according to the setup options.
;
resident_code segment
;
; Make sure that the time is correct. Do this by checking other time
; references.
;
CLK_fix_our_time proc near
debug_message '{ fix our time '
;
; Ensure that DS points to the correct segment
;
push ds ; Save old DS
push cs ; Make DS point to code segment
pop ds ;
;
; The initialisation routine should have stored pointers to a suitable
; procedure in the CLK_get_procedure_ptr word. Now call the procedure.
;
call word ptr ds:[CLK_get_procedure_ptr]
;
; Restore DS segment register and return
;
pop ds
debug_message '} '
ret
CLK_fix_our_time endp
;
; Make sure that the time used by other timers in the system is correct.
; One reason for doing this is so that the "other timers" can tell us
; the correct time next time we need to ask them.
;
CLK_set_other_timers proc near
debug_message '{ set others '
;
; Ensure that DS points to the correct segment
;
push ds ; Save old DS
push cs ; Make DS point to code segment
pop ds ;
;
; The initialisation routine should have stored a pointer to a suitable
; procedure in the CLK_set_procedures array. Now call the procedure.
;
call word ptr ds:[CLK_set_procedure_ptr]
;
; Also set the plain BIOS idea of the time.
; (CLK_set_procedure_ptr will point to a null procedure if we are
; using the plain BIOS without any other clock.)
;
call CLK_set_BIOS_time
;
; Restore DS segment register and return
;
pop ds
debug_message '} '
ret
CLK_set_other_timers endp
resident_code ends
;***********************************************************************
;
; Check the validity of the date and time passed in a write request.
; On entry, DS:SI points to the caller's data.
; On exit, carry flag will be set if the data is invalid.
;
resident_code segment
CLK_check_data_validity proc near
debug_message '{ validity check '
debug_message ' ( data: '
debug_hex_string ds,[si],6,word
debug_message ') '
;
; Check hours, minutes, seconds and hundredths of secs.
; Note funny order in which things are stored.
;
cmp byte ptr ds:[si+3], 24 ; Hours must be < 24
jnc short CLK_check_end
cmp byte ptr ds:[si+2], 60 ; Minutes must be < 60
jnc short CLK_check_end
cmp byte ptr ds:[si+5], 60 ; Seconds must be < 60
jnc short CLK_check_end
cmp byte ptr ds:[si+4], 60 ; Hundredths must be < 100
cmp al, 100
CLK_check_end:
;
; Toggle the carry flag, so this routine returns with carry set if
; there was a problem, and clear if there was no problem.
;
cmc
debug_message '} '
ret
CLK_check_data_validity endp
resident_code ends
;***********************************************************************
;
; This procedure will read the normal BIOS timer, and check whether either
; the date has changed or the time has changed by more than a few minutes
; since the last call. If necessary, it then reads some other clock (which
; has coarser granularity but higher accuracy), and re-calibrates the
; normal BIOS timer.
;
resident_code segment
CLK_get_BIOS_or_other_time proc near
;
; Ask BIOS for the new date and time
;
call CLK_get_BIOS_time
;
; See if the date has changed
;
mov ax,[CLK_date_days] ; Current date
cmp ax,[CLK_last_date] ; Previous date
jne CLK_must_get_other_time
;
; See if the time has changed by more than five minutes since
; last time we got the time from the AT BIOS battery-backed clock.
;
mov al,[CLK_time_hour] ; Current hour*60 + minute
mov ah,60
mul ah
add al,[CLK_time_min]
adc ah,0
sub ax,[CLK_last_time] ; Subtract previous time
cmp ax,5 ; Did time go forwards < 5 mins ?
jng CLK_dont_get_other_time
CLK_must_get_other_time:
;
; Read the other clock and set the normal BIOS timer to the current time.
;
call word ptr ds:[CLK_get_other_procedure_ptr]
call CLK_set_BIOS_time
CLK_dont_get_other_time:
;
; Remember the current date and time. This will be the "old" date and time
; next time the clock is read.
;
mov ax,[CLK_date_days] ; Date
mov [CLK_last_date],ax
mov al,[CLK_time_hour] ; Hours multiplied by 60
mov ah,60
mul ah
add al,[CLK_time_min] ; Plus minutes
adc ah,0
mov [CLK_last_time],ax
ret
CLK_get_BIOS_or_other_time endp
resident_code ends
;***********************************************************************
;
; Handle a AT-type computer, with clock chip support in the BIOS.
;
; Note that some of these routines are in the startup_code and some
; are in the resident_code.
;
startup_code segment
;
; Check for AT-type BIOS support of the clock chip.
;
; BIOS interrupt 1A function 4 should get the date from the real time clock
; if the computer has an AT-type BIOS. The call should return invalid data if
; the BIOS does not support that function.
;
; Return with CARRY set if no AT BIOS support for clock chip.
;
CLK_find_AT_BIOS proc near
assume ds:cgroup
debug_message '{ find AT BIOS '
;
; Do an interrupt 1A function 4 call. (Get date from real time clock
; on an AT machine.)
;
xor cx,cx ; This will help find errors
xor dx,dx ;
mov ah,04h ; Ask BIOS for the date
int 1Ah ;
;
; Check that the results are reasonable. If not, then it cannot
; have an AT-compatible BIOS.
; The results are all in BCD.
;
cmp ch,19h ; Is the century reasonable ?
jb CLK_find_no_AT ; no
cmp cl,99h ; Is the year reasonable ?
ja CLK_find_no_AT ; no
cmp dh,12h ; Is the month reasonable ?
ja CLK_find_no_AT ; no
cmp dl,31h ; Is the day reasonable ?
ja CLK_find_no_AT ; no
;
; Return with no carry if all is correct
;
clc
debug_message '(ok) } '
ret
;
; If there is no AT BIOS clock support, return with carry flag set.
;
CLK_find_no_AT:
stc
debug_message '(failed) } '
ret
CLK_find_AT_BIOS endp
startup_code ends
resident_code segment
;
; Get the time from AT BIOS.
;
CLK_get_AT_BIOS_time proc near
assume ds:cgroup
debug_message '{ get AT BIOS '
;
; BIOS interrupt 1Ah functions 2 and 4 get time and date from AT BIOS
;
mov ah,02h ; Get time
int 1Ah ;
mov al,ch ; Save it
call conv_BCD_binary ;
mov [CLK_time_hour],al ;
mov al,cl ;
call conv_BCD_binary ;
mov [CLK_time_min],al ;
mov al,dh ;
call conv_BCD_binary ;
mov [CLK_time_sec],al ;
mov [CLK_time_hsec],0 ; There are no hundredths ???
mov ah,04h ; Get date
int 1Ah ;
mov al,ch ; Save it
call conv_BCD_binary ;
mov [CLK_date_century],al ;
mov al,cl ;
call conv_BCD_binary ;
mov [CLK_date_year],al ;
mov al,dh ;
call conv_BCD_binary ;
mov [CLK_date_month],al ;
mov al,dl ;
call conv_BCD_binary ;
mov [CLK_date_day],al ;
;
; The date is in century, year, month, day form
; so it must be converted to a number of days since 1-Jan-1980.
;
call CLK_conv_ccyymmdd_days
ret
debug_message '} '
CLK_get_AT_BIOS_time endp
;
; Tell the AT BIOS to set the time.
;
CLK_set_AT_BIOS_time proc near
assume ds:cgroup
debug_message '{ set AT BIOS '
;
; Convert the date from days since 1-Jan-1980 to year, month, day
;
call CLK_conv_days_ccyymmdd
;
; Set the AT BIOS clock.
; Everything must be in BCD.
;
mov al,[CLK_time_hour]
call conv_binary_BCD
mov ch,al
mov al,[CLK_time_min]
call conv_binary_BCD
mov cl,al
mov al,[CLK_time_sec]
call conv_binary_BCD
mov dh,al
xor dl,dl ; Not daylight saving time
mov ah,3
int 1Ah
mov al,[CLK_date_century]
call conv_binary_BCD
mov ch,al
mov al,[CLK_date_year]
call conv_binary_BCD
mov cl,al
mov al,[CLK_date_month]
call conv_binary_BCD
mov dh,al
mov al,[CLK_date_day]
call conv_binary_BCD
mov dl,al
mov ah,5
int 1Ah
debug_message '} '
ret
CLK_set_AT_BIOS_time endp
resident_code ends
;***********************************************************************
;
; Handle a National Semiconductor MM58167AN clock chip.
; Most add-on clock cards use this chip.
;
; There are at least three different conventions for using the chip
; RAM registers to remember the date. We use the method used by the
; TIMER program provided with some add-on clock cards; this is not
; the same as the method used by the SETCLOCK and GETCLOCK programs
; provided with some other add-on clock cards, and also differs from
; the usage suggested in the chip documentation.
;
; Note that some of these routines are in the startup_code and some
; are in the resident_code.
;
; There is a word in the resident_data segment to say whether or
; not to use these routines, and what base address to use for the
; clock chip.
;
startup_code segment
;
; Look for the MM58167AN clock chip.
;
; The chip is usually addressed from I/O port 00C0, 0240, 02C0 or 0340,
; so we look in all these places, unless the user specified a location,
; in which case look there only. The user can also explicitly say don't
; use the clock chip.
;
; On entry, CLK_chip_IO_base is zero if chip will not be used, or
; 0FFFFh to search for chip in standard places, or some other value
; representing the chip's actual IO base address.
;
; Return with CARRY set if no clock chip.
; CLK_chip_IO_base will be set to the start address if there is a chip,
; or to zero if there is none.
;
CLK_find_MM58167AN proc near
assume ds:cgroup
debug_message '{ find MM58167AN chip '
;
; Check whether the user told us not to use the clock chip
;
mov ax,[CLK_chip_IO_base] ; What the user said
or ax, ax ; If zero then don't use clock
je CLK_find_no_MM58167AN
;
; Check whether user said where the chip resides in the IO map
;
cmp ax,0FFFFh ; If not FFFF, look there only
jne CLK_find_MM58167AN_last_resort
;
; Look in the standard places
;
mov word ptr [CLK_chip_IO_base],0240h ; Base address
call CLK_check_MM58167AN
jnc CLK_find_MM58167AN_exit ; Exit if found
mov word ptr [CLK_chip_IO_base],02C0h ; Base address
call CLK_check_MM58167AN
jnc CLK_find_MM58167AN_exit ; Exit if found
mov word ptr [CLK_chip_IO_base],0340h ; Base address
call CLK_check_MM58167AN
jnc CLK_find_MM58167AN_exit ; Exit if found
mov word ptr [CLK_chip_IO_base],00C0h ; Base address
CLK_find_MM58167AN_last_resort:
call CLK_check_MM58167AN ; See if there is a clock chip
jc CLK_find_no_MM58167AN ; Error if not found
CLK_find_MM58167AN_exit:
debug_message '( ok '
debug_hex_word <word ptr [CLK_chip_IO_base]>
debug_message ' ) } '
ret
;
; If there is no clock chip, set the carry flag and zero the base address.
;
CLK_find_no_MM58167AN:
mov word ptr [CLK_chip_IO_base],0
debug_message '( failed ) } '
stc
ret
CLK_find_MM58167AN endp
;
; Check if there is an MM58167AN clock chip at the IO base address
; specified in CLK_chip_IO_base. Return with CARRY if there is no chip.
;
CLK_check_MM58167AN proc near
assume ds:cgroup
debug_message '{ check MM58167AN ( base '
debug_hex_word <word ptr [CLK_chip_IO_base]>
debug_message ' ) '
;
; Check the first seven registers, to ensure that they
; contain BCD data within the correct range.
;
mov dx,[CLK_chip_IO_base] ; Get chip base address
in al,dx ; 00: 1/10000 seconds
call CLK_check_BCD
jc CLK_check_no_MM58167AN
inc dx ; 01: 1/10 and 1/100 seconds
in al,dx
call CLK_check_BCD
jc CLK_check_no_MM58167AN
inc dx ; 02: seconds
in al,dx
call CLK_check_BCD
jc CLK_check_no_MM58167AN
cmp al,59h ; Must be less than 60
ja CLK_check_no_MM58167AN
inc dx ; 03: minutes
in al,dx
call CLK_check_BCD
jc CLK_check_no_MM58167AN
cmp al,59h ; Must be less than 60
ja CLK_check_no_MM58167AN
inc dx ; 04: hours
in al,dx
call CLK_check_BCD
jc CLK_check_no_MM58167AN
cmp al,23h ; Must be less than 24
ja CLK_check_no_MM58167AN
inc dx ; 05: day of week
in al,dx
test al,al ; Must be between 1 and 7
jz CLK_check_no_MM58167AN
cmp al,07h
ja CLK_check_no_MM58167AN
inc dx ; 06: day of month
in al,dx
test al,al ; Must be between 1 and 31
jz CLK_check_no_MM58167AN
cmp al,31h
ja CLK_check_no_MM58167AN
inc dx ; 07: month
in al,dx
test al,al ; Must be between 1 and 12
jz CLK_check_no_MM58167AN
cmp al,12h
ja CLK_check_no_MM58167AN
;
; It certainly looks like an MM58167AN clock chip.
; Return with carry flag clear.
;
clc
debug_message '(ok) } '
ret
CLK_check_no_MM58167AN:
;
; It is definitely not an MM58167AN clock chip.
; Return with carry flag set.
;
stc
debug_message '( failed ) } '
ret
CLK_check_MM58167AN endp
startup_code ends
resident_code segment
;
; Get the time from clock chip
;
CLK_get_MM58167AN_time proc near
assume ds:cgroup
debug_message '{ get MM58167AN '
;
; Read all the relevant values from the chip's registers
;
; The "last month" value used by the TIMER program supplied with
; some clock cards actually stores the year in register 09 and the
; last month in register 08. We will do the same, for compatibility.
; The last month value is OR( SHL( MAX(month,7),4 ), 80h).
;
mov dx,[CLK_chip_IO_base] ; Address of the first register
inc dx ; 00: ten thousandths of seconds
; (ignore)
in al,dx ; 01: hundredths of seconds
debug_message '( centi '
debug_hex_byte al
debug_message ' BCD ) '
call conv_BCD_binary ; convert from BCD
mov [CLK_time_hsec],al ; save
inc dx ; 02: seconds
in al,dx
debug_message '( secs '
debug_hex_byte al
debug_message ' BCD ) '
call conv_BCD_binary
mov [CLK_time_sec],al
inc dx ; 03: minutes
in al,dx
debug_message '( mins '
debug_hex_byte al
debug_message ' BCD ) '
call conv_BCD_binary
mov [CLK_time_min],al
inc dx ; 04: hours
in al,dx
debug_message '( hours '
debug_hex_byte al
debug_message ' BCD ) '
call conv_BCD_binary
mov [CLK_time_hour],al
inc dx ; 05: day of week (ignore)
inc dx ; 06: day of month
in al,dx
debug_message '( day of month '
debug_hex_byte al
debug_message ' BCD ) '
call conv_BCD_binary
mov [CLK_date_day],al
inc dx ; 07: month
in al,dx
debug_message '( month '
debug_hex_byte al
debug_message ' BCD ) '
mov bl,al ; (save BCD month value)
call conv_BCD_binary
mov [CLK_date_month],al
inc dx ; 08: RAM : last month
in al,dx
debug_message '( last month '
debug_hex_byte al
debug_message ' ) '
mov bh,al ; (save last month value)
mov al,bl ; find MAX(7,current month)
cmp al,7
jng CLK_get_MM58167AN_month_7
mov al,7
CLK_get_MM58167AN_month_7:
mov cl,4 ; Shift to high nybble
shl al,cl
or al,80h ; Set high bit
debug_message '( new last month value '
debug_hex_byte al
debug_message ' ) '
mov bl,al ; Save new last month
out dx,al ; Store new last month
inc dx ; 09: year
in al,dx
debug_message '( year '
debug_hex_byte al
debug_message ' BCD ) '
;
; See if the year has changed.
;
cmp bl,bh ; Is current month smaller than
; previous month ?
jae CLK_get_MM58167AN_same_year
debug_message '( year has changed ) '
inc al ; Increment year
daa
out dx,al ; Store new year into chip
CLK_get_MM58167AN_same_year:
call conv_BCD_binary
mov [CLK_date_year],al
;
; Choose a suitable century
;
mov ah,19 ; Assume the year is 19xx.
cmp al,80 ; If last two digits less than 80
; then the year is 20xx
jnb CLK_get_MM58167AN_century
inc ah
CLK_get_MM58167AN_century:
debug_message '( century '
debug_hex_byte ah
debug_message' ) '
mov [CLK_date_century],ah
;
; Convert the date to number of days since 1-Jan-1980
;
call CLK_conv_ccyymmdd_days
;
; Finished getting time from chip
;
ret
debug_message '} '
CLK_get_MM58167AN_time endp
;
; Set the time on the clock chip.
;
CLK_set_MM58167AN_time proc near
assume ds:cgroup
debug_message '{ set MM58167AN '
;
; Convert date to correct format
;
call CLK_conv_days_ccyymmdd ; Year, month, day from day number
call CLK_conv_days_weekday ; Find current day of week
;
; Store data into clock chip
;
; The "last month" value used by the TIMER program supplied with
; some clock cards actually stores the year in register 09 and the
; last month in register 08. We will do the same, for compatibility.
; The last month value is OR( SHL( MAX(month,7),4 ), 80h).
;
mov dx,[CLK_chip_IO_base] ; Base address of clock chip
xor al,al ; 00: 1/10000 secs (zero)
out dx,al
debug_message '( zero 1/10000 sec ) '
inc dx ; 01: 1/100 secs
mov al,[CLK_time_hsec]
call conv_binary_BCD
debug_message '( centi '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
inc dx ; 02: seconds
mov al,[CLK_time_sec]
call conv_binary_BCD
debug_message '( secs '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
inc dx ; 03: minutes
mov al,[CLK_time_min]
call conv_binary_BCD
debug_message '( mins '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
inc dx ; 04: hours
mov al,[CLK_time_hour]
call conv_binary_BCD
debug_message '( hour '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
inc dx ; 05: day of week
mov al,[CLK_date_weekday]
inc al ; Chip uses Sunday=1, not =0
debug_message '( weekday '
debug_hex_byte al
debug_message ' ) '
out dx,al
inc dx ; 06: day of month
mov al,[CLK_date_day]
call conv_binary_BCD
debug_message '( day of month '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
inc dx ; 07: month
mov al,[CLK_date_month]
mov bl,al
call conv_binary_BCD
debug_message '( month '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
inc dx ; 08: RAM : last month
mov al,bl ; Get current month
cmp al,7 ; find MAX(7,current month)
jng CLK_set_MM58167AN_month_7
mov al,7
CLK_set_MM58167AN_month_7:
mov cl,4 ; Shift to high nybble
shl al,cl
or al,80h ; Set high bit
debug_message '( new last month value '
debug_hex_byte al
debug_message ' ) '
out dx,al ; Store new last month
inc dx ; 09: RAM : year
mov al,[CLK_date_year]
call conv_binary_BCD
debug_message '( year '
debug_hex_byte al
debug_message ' BCD ) '
out dx,al
;
; Finished
;
ret
debug_message '} '
CLK_set_MM58167AN_time endp
resident_code ends
;***********************************************************************
;
; Handle an MSM6242 clock chip.
;
; Note that some of these routines are in the startup_code and some
; are in the resident_code.
;
; There is a word in the resident_data segment to say whether or
; not to use these routines, and what base address to use for the
; clock chip.
;
startup_code segment
;
; Look for the MSM6242 clock chip.
;
; The chip is usually addressed from I/O port 02C0,
; so we look there, unless the user specified a different location,
; in which case look there only. The user can also explicitly say don't
; use the clock chip.
;
; On entry, CLK_chip_IO_base is zero if chip will not be used, or
; 0FFFFh to search for chip in standard places, or some other value
; representing the chip's actual IO base address.
;
; Return with CARRY set if no clock chip.
; CLK_chip_IO_base will be set to the start address if there is a chip,
; or to zero if there is none.
;
CLK_find_MSM6242 proc near
assume ds:cgroup
debug_message '{ find MSM6242 chip '
;
; Check whether the user told us not to use the clock chip
;
mov ax,[CLK_chip_IO_base] ; What the user said
or ax, ax ; If zero then don't use clock
je CLK_find_no_MSM6242
;
; Check whether user said where the chip resides in the IO map
;
cmp ax,0FFFFh ; If not FFFF, look there only
jne CLK_find_MSM6242_last_resort
;
; Look in the standard places
;
mov word ptr [CLK_chip_IO_base],02C0h ; Base address
CLK_find_MSM6242_last_resort:
call CLK_check_MSM6242 ; See if there is a clock chip
jc CLK_find_no_MSM6242 ; Error if not found
CLK_find_MSM6242_exit:
debug_message '( ok '
debug_hex_word <word ptr [CLK_chip_IO_base]>
debug_message ' ) } '
ret
;
; If there is no clock chip, set the carry flag and zero the base address.
;
CLK_find_no_MSM6242:
mov word ptr [CLK_chip_IO_base],0
debug_message '( failed ) } '
stc
ret
CLK_find_MSM6242 endp
;
; Check if there is an MSM6242 clock chip at the IO base address
; specified in CLK_chip_IO_base. Return with CARRY if there is no chip.
;
CLK_check_MSM6242 proc near
assume ds:cgroup
debug_message '{ check MSM6242 ( base '
debug_hex_word <word ptr [CLK_chip_IO_base]>
debug_message ' ) '
;
; Check the first twelve registers, to ensure that they
; contain data within the correct range. The high 4 bits
; should be ignored.
;
mov dx,[CLK_chip_IO_base] ; Get chip base address
in al,dx ; 00: 1's of seconds
and al, 0Fh
cmp al, 10
ja CLK_check_no_MSM6242
inc dx ; 01: 10's of seconds
in al,dx
and al, 0Fh
cmp al, 10
ja CLK_check_no_MSM6242
inc dx ; 02: 1's of minutes
in al,dx
and al, 0Fh
cmp al, 10
ja CLK_check_no_MSM6242
inc dx ; 03: 10's of minutes
in al,dx
and al, 0Fh
cmp al, 10
ja CLK_check_no_MSM6242
inc dx ; 04: 1's of hours
in al,dx
and al, 0Fh
cmp al, 10
ja CLK_check_no_MSM6242
inc dx ; 05: 10's of hours
in al,dx
and al, 0Fh
cmp al, 2
ja CLK_check_no_MSM6242
inc dx ; 06: 1's of day of month
in al,dx
and al, 0Fh
cmp al,10
ja CLK_check_no_MSM6242
inc dx ; 07: 10's of day of month
in al,dx
and al, 0Fh
cmp al,3
ja CLK_check_no_MSM6242
inc dx ; 08: 1's of month number
in al,dx
and al, 0Fh
cmp al,10
ja CLK_check_no_MSM6242
inc dx ; 09: 10's of month number
in al,dx
and al, 0Fh
cmp al,1
ja CLK_check_no_MSM6242
inc dx ; 0A: 1's of year (within century)
in al,dx
and al, 0Fh
cmp al,10
ja CLK_check_no_MSM6242
inc dx ; 0B: 10's of year
in al,dx
and al, 0Fh
cmp al,10
ja CLK_check_no_MSM6242
inc dx ; 0C: day of week (Sun=1, Sat=7))
in al,dx
and al, 0Fh
jz CLK_check_no_MSM6242
cmp al,7
ja CLK_check_no_MSM6242
;
; It certainly looks like an MSM6242 clock chip.
; Return with carry flag clear.
;
clc
debug_message '(ok) } '
ret
CLK_check_no_MSM6242:
;
; It is definitely not an MSM6242 clock chip.
; Return with carry flag set.
;
stc
debug_message '( failed ) } '
ret
CLK_check_MSM6242 endp
startup_code ends
resident_code segment
;
; Get the time from clock chip
;
CLK_get_MSM6242_time proc near
assume ds:cgroup
debug_message '{ get MSM6242 '
;
; Read all the relevant values from the chip's registers
;
mov [CLK_time_hsec], 0 ; no hundredths available
mov dx,[CLK_chip_IO_base] ; Get chip base address
in al,dx ; 00: 1's of seconds
mov ah, al
inc dx ; 01: 10's of seconds
in al, dx
xchg ah, al
and ax, 0F0Fh ; ignore junk bits
aad ; AAD sets AL := AL + 10*AH.
; Now if Intel would just *document* the
; extension to bases other than 10, we
; could use it for other interesting things...
debug_message '( secs '
debug_hex_byte al
debug_message ' ) '
mov [CLK_time_sec],al
inc dx ; 02: 1's of minutes
in al,dx
mov ah, al
inc dx ; 03: 10's of minutes
in al,dx
xchg ah, al
and ax, 0F0Fh
aad
debug_message '( mins '
debug_hex_byte al
debug_message ' ) '
mov [CLK_time_min],al
inc dx ; 04: 1's of hours
in al,dx
inc dx ; 05: 10's of hours
in al,dx
mov ah, al
xchg ah, al
and ax, 0F0Fh
aad
debug_message '( hour '
debug_hex_byte al
debug_message ' ) '
mov [CLK_time_hour],al
inc dx ; 06: 1's of day of month
in al,dx
mov ah, al
inc dx ; 07: 10's of day of month
in al,dx
xchg ah, al
and ax, 0F0Fh
aad
debug_message '( day '
debug_hex_byte al
debug_message ' ) '
mov [CLK_date_day],al
inc dx ; 08: 1's of month number
in al,dx
mov ah, al
inc dx ; 09: 10's of month number
in al,dx
xchg ah, al
and ax, 0F0Fh
aad
debug_message '( month '
debug_hex_byte al
debug_message ' ) '
mov [CLK_date_month],al
inc dx ; 0A: 1's of year (within century)
in al,dx
mov ah, al
inc dx ; 0B: 10's of year
in al,dx
xchg ah, al
and ax, 0F0Fh
aad
debug_message '( year '
debug_hex_byte al
debug_message ' ) '
mov [CLK_date_year],al
;inc dx ; 0C: day of week (Sun=1,Sat=7) (ignore)
;
; Choose a suitable century
;
mov ah,19 ; Assume the year is 19xx.
cmp al,80 ; If last two digits less than 80
; then the year is 20xx
jnb CLK_get_MSM6242_century
inc ah
CLK_get_MSM6242_century:
debug_message '( century '
debug_hex_byte ah
debug_message' ) '
mov [CLK_date_century],ah
;
; Convert the date to number of days since 1-Jan-1980
;
call CLK_conv_ccyymmdd_days
;
; Finished getting time from chip
;
ret
debug_message '} '
CLK_get_MSM6242_time endp
;
; Set the time on the clock chip.
;
CLK_set_MSM6242_time proc near
assume ds:cgroup
debug_message '{ set MSM6242 '
;
; Convert date to correct format
;
call CLK_conv_days_ccyymmdd ; Year, month, day from day number
call CLK_conv_days_weekday ; Find current day of week
;
; Store data into clock chip
;
mov dx,[CLK_chip_IO_base] ; Get chip base address
mov al,[CLK_time_sec] ; 00 and 01: secs
debug_message '( secs '
debug_hex_byte al
debug_message ') '
aam ; puts high digit in AH, low digit in AL
out dx, al
inc dx
mov al, ah
out dx, al
inc dx
mov al,[CLK_time_min] ; 02 and 03: mins
debug_message '( mins '
debug_hex_byte al
debug_message ') '
aam
out dx, al
inc dx
mov al, ah
out dx, al
inc dx
mov al,[CLK_time_min] ; 04 and 05: hours
debug_message '( hours '
debug_hex_byte al
debug_message ') '
aam
out dx, al
inc dx
mov al, ah
out dx, al
inc dx
mov al,[CLK_date_day] ; 06 and 07: day of month
debug_message '( day '
debug_hex_byte al
debug_message ') '
aam
out dx, al
inc dx
mov al, ah
out dx, al
inc dx
mov al,[CLK_date_month] ; 08 and 09: month
debug_message '( month '
debug_hex_byte al
debug_message ') '
aam
out dx, al
inc dx
mov al, ah
out dx, al
inc dx
mov al,[CLK_date_year] ; 0A and 0B: year
debug_message '( year '
debug_hex_byte al
debug_message ') '
aam
out dx, al
inc dx
mov al, ah
out dx, al
inc dx
mov al,[CLK_date_weekday] ; 0C: day of week
inc al ; Chip uses Sunday=1, not =0
debug_message '( weekday '
debug_hex_byte al
debug_message ' ) '
out dx,al
;
; Finished
;
ret
debug_message '} '
CLK_set_MSM6242_time endp
resident_code ends
;***********************************************************************
;
; Subroutines to read and set the BIOS timer.
;
; All these routines are in the resident_code.
;
; There are calibration values in the resident_data that may (one day)
; be controllable from the command line in the CONFIG.SYS file. At present
; they are fixed at assembly time.
;
resident_data segment
;
; These values are used to calibrate the BIOS timer.
; CLK_tick_65536 should contain the number of ticks that will occur
; in 65536 seconds. This should be 1193180 (hex 001234DC), because
; the clock input to the 8253 timer chip is 1.19318 MHz.
; CLK_tick_65536_20 is one twentieth of the number of ticks per 65536
; seconds. It should be 59659 (hex E90B).
; CLK_tick_day should contain the number of ticks per day. This should
; be 1573040 (hex 001800B0).
; All these values should be changed simultaneously! There is no check
; to ensure that they are self-consistent.
; Note that unless the BIOS timer interrupt server is modified, changing the
; ticks per day value here will not have the desired effect.
; Even if these values are modified, some sections of the code assume that
; the number of ticks per second is 18.2. For this reason, small adjustments
; for calibration purposes are OK, but large adjustments may cause trouble.
;
CLK_tick_65536 dd 1193180 ; Number of ticks in 65536 seconds
CLK_tick_65536_20 dw 59659 ; CLK_tick_65536 divided by 20
CLK_tick_day dd 1573040 ; Number of time-base ticks per day
;
; Keep the last BIOS tick count, to check for time going backwards.
; The initial value of zero is chosen so that the first time
; CLK_get_BIOS_time is called, it doesn't think time has gone backwards.
;
CLK_last_tick dd 0
;
; The last date, hour and minute at which the timer was called.
; This is needed when the AT BIOS or some other coarse graoned clock
; is used.
; We don't bother updating this data if a fine grain clock
; chip is used.
;
; When we have to deal with a coarse grain clock chip, we can do better
; by usually using the normal BIOS timer (to get finer granularity)
; and whenever the get-time procedure is called more than a few minutes
; after the previous call, or when the date has changed, we update the
; BIOS timer from the chip, to get reasonable long term accuracy.
; The initial values in the DW's below ensure that the genuine chip
; clock is used the first time.
;
CLK_last_date dw -10 ; The date
CLK_last_time dw -10 ; Hours*60 + minutes
resident_data ends
resident_code segment
;
; Make the BIOS timer correspond to our time.
;
CLK_set_BIOS_time proc near
assume ds:cgroup
debug_message '{ set BIOS '
;
; First convert the time to a number of seconds. Ignore the hundredths
; of seconds until later.
;
mov al,CLK_time_hour ; Multiply hours by 60
debug_message '( hour '
debug_hex_byte al
debug_message ' ) '
mov bx,60
mul bl
mov cl,CLK_time_min ; Add minutes
debug_message '( min '
debug_hex_byte cl
debug_message ' ) '
xor ch,ch
add ax,cx
; Now AX is number of minutes
mul bx ; Multiply total minutes by 60
mov cl,CLK_time_sec ; Add seconds
debug_message '( sec '
debug_hex_byte cl
debug_message ' ) '
add ax,cx
adc dx,0
; Combined DX_AX is now the number of seconds.
debug_message '( total secs '
debug_hex_word dx
debug_hex_word ax
debug_message ' ) '
;
; Convert the number of seconds to a number of ticks.
; This requires multiplying by 1193180, then dividing by 65536.
;
; This is done by multiplying the 32-bit value in DX_AX by the 21-bit
; value 1193180 (hexadecimal 001234DC), and discarding the lowest 16 bits
; of the result. (This works because 65536 is 2^16.)
;
;
; The value in DX_AX can never be greater than 24*60*60,
; which is 86400, or hexadecimal 00015180. After
; multiplication by 1193180, the result will not be larger
; than about 103e9, which is representable in 37 bits.
; As the result will then be divided by 65536 (which is 2^16),
; we need not even calculate the lowest 16 bits. The result
; will therefore require 21 bits of storage, and 32 bits
; will actually be used.
;
; Let the low 16 bits of the multiplicand (now in DX_AX)
; be A0. Let the high 16 bits be A1.
; Let the low 16 bits of the multiplier (1193180) be B0.
; Let the high 16 bits be B1.
; In general, multiplying two 32-bit values could yield
; a 64-bit product.
; Let the four 16-bit sections of the product be C0, C1,
; C2 and C3, with C0 being the lowest 16 bits.
; Let L() and H() denote the low and high 16 bits of a
; 32-bit value.
; Let D0, D1, D2 and D3 be 32-bit intermediate results.
; Note that C3 will be zero.
; C0 will be ignored, unless it is greater than 2^15, in
; which case the total will be rounded up.
;
; D0 = A0*B0
; C0 = L( D0 )
; D1 = H( D0 ) + L( A0*B1 ) + L( A1*B0 )
; C1 = L( D1 )
; D2 = H( D1 ) + H( A0*B1 ) + H( A1*B0 ) + L( A1*B1 )
; C2 = L( D2 )
; D3 = H( D2 ) + H( A1*B1 )
; C3 = L( D3 )
;
; C3 will be zero, so D3 need not be calculated.
;
; Save the value currently in DX_AX
mov t1,dx ; T1 := A1
mov t2,ax ; T2 := A0
;
; Calculate D0 = A0*B0
; AX is already = A0
mov bx,word ptr [CLK_tick_65536] ; BX := B0
mul bx
;
; We don't need L(D0), which is in AX, but we must
; save H(D0), which is in DX. Before doing so, check
; if L(D0) is greater that hex 7FFF, in which case the
; result must be rounded up by incrementing DX. Note that
; DX cannot be larger than hex FFFE, so incrementing it
; here cannot cause overflow problems.
mov cx,7FFFh
cmp cx,ax ; Set carry flag if AX > 7FFFh
adc dx,0 ; Increment DX if necessary
mov t3,dx ; T3 := H(D0)
;
; Multiply A1*B0
mov ax,t1 ; AX := A1
; BX is already = B0
mul bx
mov t4,dx ; T4 := H(A1*B0)
mov t5,ax ; T5 := L(A1*B0)
;
; Multiply A0*B1
mov ax,t2 ; AX := A0
mov bx,word ptr [CLK_tick_65536+2] ; BX := B1
mul bx
mov t2,dx ; T2 := H(A0*B1)
;
; Calculate D1 = H(D0) + L(A0*B1) + L(A1*B0)
; AX is already = L(A0*B1)
xor dx,dx
add ax,t3 ; add H(D0)
adc dx,0
add ax,t5 ; add L(A1*B0)
adc dx,0
mov t3,dx ; T3 := H(D1)
mov t5,ax ; T5 := C1 { = L(D1) }
;
; Multiply A1*B1
mov ax,t1 ; AX := A1
; BX is already = B1
mul bx
;
; H(A1*B1) should be zero, and is not needed.
; Calculate D2 = H(D1) + L(A1*B1) + H(A1*B0) + H(A0*B1).
; H(D2) will be zero, and need not be calculated.
; AX is already = L(A1*B1)
add ax,t3 ; add H(D1)
add ax,t4 ; add H(A1*B0)
add ax,t2 ; add H(A0*B1)
mov cx,ax ; CX := L(D2) { = C2 }
;
; CX is high word, T5 is low word of result
debug_message '( ticks from secs '
debug_hex_word cx
debug_hex_word t5
debug_message ' ) '
;
; We now have a number of clock ticks, but it ignores the hundredths of
; seconds. Treating the hundredths of seconds exactly would have required
; multiplying a 32-bit value by 100 and dividing a 32-bit value by 100, in
; addition to the code already used. That task is not difficult to code
; but it does add much extra code for very little benefit.
;
; An approximate number of extra ticks is added to the count now, to handle
; the hundredths of seconds. This approximate value is obtained simply by
; multiplying the hundredths by 10 and dividing by 55. Just before dividing
; by 55, add 27 to make the division round instead of truncating.
;
mov al,CLK_time_hsec ; Calculate ticks for centi-secs
debug_message '( centi '
debug_hex_byte al
debug_message ' ) '
mov ah,10
mul ah
add ax,27
mov bl,55
div bl
xor ah,ah ; Discard the remainder
add ax,t5 ; Add to total
adc cx,0 ;
; Now the total number of ticks is in CX_AX
;
; Remember the tick count for later
;
mov word ptr ds:[CLK_last_tick+2], cx
mov word ptr ds:[CLK_last_tick], ax
;
; Call the BIOS "set timer" interrupt
;
mov dx,ax ; DX is low 16 bits of count
; CX is already high 16 bits
debug_message '( total ticks '
debug_hex_cx_dx
debug_message ' ) '
mov ah,1 ; INT 1Ah function 1
int 1Ah ; make BIOS set its timer
;
; Finished, at last
;
debug_message '} '
ret
CLK_set_BIOS_time endp
;
; Make our time correspond to the BIOS timer.
;
CLK_get_BIOS_time proc near
assume ds:cgroup
debug_message '{ get BIOS '
;
; Call the BIOS "get timer" interrupt
;
xor ah,ah ; INT 1Ah function 0
int 1Ah ;
debug_message '( total ticks '
debug_hex_cx_dx
debug_message ' ) '
; Tick count is now in combined CX_DX
;
; If time seems to have run backwards (AL=0 means date didn't change, but
; current tick count is less than remembered tick count) then assume the
; date changed.
;
or al,al ; if AL <> 0 then
jnz CLK_get_BIOS_date_change ; the date has changed
cmp cx, word ptr ds:[CLK_last_tick+2] ; check high word
ja CLK_get_BIOS_date_nochange ; time went forwards
jb CLK_get_BIOS_time_backwards ; time went backwards
cmp dx, word ptr ds:[CLK_last_tick] ; check low word
jnb CLK_get_BIOS_date_nochange ; time went forwards
CLK_get_BIOS_time_backwards:
mov al, 1 ; assume date changed
;
; Increment the date if necessary.
;
; NOTE: Just add AL to the current date. AL is guaranteed to be zero if
; the date has not changed. On the IBM PC, AL will be 1 if the date has
; changed (even if the date has changed more than once). With a non-IBM
; BIOS, AL might have some other value. The best possible situation is
; AL = (number of times the date has changed). The worst possible situation
; is AL = (some arbitrary non-zero value).
;
CLK_get_BIOS_date_change:
xor ah,ah
add CLK_date_days, ax
CLK_get_BIOS_date_nochange:
;
; Remember the tick count for next time
;
mov word ptr ds:[CLK_last_tick+2],cx
mov word ptr ds:[CLK_last_tick],dx
;
;
; If for some reason the BIOS tick count is too large, adjust it to the
; maximum number of ticks per day.
;
cmp cx,word ptr ds:[CLK_tick_day+2] ; Check high word
jb CLK_get_BIOS_ok
ja CLK_get_BIOS_too_big
cmp dx,word ptr ds:[CLK_tick_day] ; Check high word
jb CLK_get_BIOS_ok
CLK_get_BIOS_too_big:
mov cx,word ptr ds:[CLK_tick_day+2] ; Set to max count
mov dx,word ptr ds:[CLK_tick_day]
dec dx ; And subtract 1
sbb cx,0
debug_message '( total adjusted to '
debug_hex_cx_dx
debug_message ' ) '
CLK_get_BIOS_ok:
;
; Now convert the tick count in CX_DX to a number of seconds.
; This requires multiplying by 65536 and dividing by 1193180.
; The remainder after the division will be used to determine the number
; of hundredths of seconds.
;
; Multiplication by 65536 is done simply by appending sixteen zero bits
; to the result (i.e., by shifting the result left 16 bits). This works
; because 65536 is 2^16. The 48-bit result (which will actually never be
; larger than hex 001800B00000) is then divided by the 32-bit value
; 1193180 (hex 001234DC).
;
; Dividing by a 32-bit value is complicated, but 1193180 factorises into
; 59659*20. It is then much simpler to divide by 59659, which can be
; represented in 16 bits. The result of this division will be a 32-bit
; value (no larger than hex 001A5E00), representing twenty times the
; number of seconds.
;
;
; Let A0, A1 and A2 be the three 16-bit portions making
; up the dividend. A0, the least significant 16 bits,
; will be zero. A1 and A2 are currently stored in DX and
; CX, as returned by the BIOS.
; Let B be the 16-bit divisor.
; Let C0, C1 and C2 be three 16-bit sections of the quotient.
; (In fact, C2 will be zero).
; Let D be the 16-bit remainder.
; Let (Q0,R0), (Q1,R1) and (Q2,R2) be pairs of 16-bit
; quotients and remainders resulting from division of a
; 32-bit dividend by a 16-bit divisor.
;
; (Q2,R2) = A2 / B
; C2 = Q2
; A2 will be less than B, so C2 = 0 and R2 = A2
; (Q1,R1) = (2^16*R2 + A1) / B
; C1 = Q1
; (Q0,R0) = (2^16*R1 + A0) / B
; C0 = Q0
; D = R0
;
; There is no need to calculate (Q2,R2).
; Calculate (Q1,R1) now.
mov ax,dx ; AX := A1
mov dx,cx ; DX := A2
mov bx,[CLK_tick_65536_20]
div bx
mov cx,ax ; CX := Q1 { = C1 }
;
; Calculate (Q0,R0)
xor ax,ax ; AX := A0 { = 0 }
div bx
;
; Now C0 is in AX, C1 is in CX and D is in DX.
;
; We now have the number of twentieths of seconds.
; Divide by twenty to get number of seconds, and multiply the
; remainder from that division by 5 to get the number of extra
; hundredths.
;
;
; This is basically the same as the previous long division,
; except that the dividend is only 32 bits, and the low part
; is not zero.
;
; Calculate (Q1,R1)
xchg ax,cx ; AX := A1, CX := A0
xor dx,dx ; DX := A2 = 0
mov bx,20
div bx
;
; Calculate (Q0,R0)
xchg ax,cx ; CX := Q1 { = C1 }, AX := A0
div bx
;
; Now C0 is in AX, C1 is in CX and D is in DX.
debug_message '( integer secs '
debug_hex_word cx
debug_hex_word ax
debug_message ' spare ticks '
debug_hex_word dx
debug_message ' ) '
;
; Multiply the remainder (the number of spare twentieths) by 5
; to get the number of spare hundredths
;
xchg ax,dx ; AX := remainder, DX := C0
mov ah,5
mul ah
debug_message '( centi '
debug_hex_byte al
debug_message ' ) '
mov CLK_time_hsec,al ; Store the number of hundredths
;
; Divide by 60 to get the total number of minutes. The remainder will
; be the number of loose seconds.
;
mov ax,dx ; AX := low 16 bits
mov dx,cx ; DX := high 16 bits
mov bx,60
div bx ; 32-bit dividend
debug_message '( integer mins '
debug_hex_word ax
debug_message ' remainder '
debug_hex_word dx
debug_message ' ) '
debug_message '( secs '
debug_hex_byte dl
debug_message ' ) '
mov CLK_time_sec,dl ; Remainder is num. seconds
;
; Divide by 60 to get the number of hours. The remainder will be
; the number of minutes.
;
div bl ; 16-bit dividend
debug_message '( min '
debug_hex_byte ah
debug_message ' ) '
debug_message '( hour '
debug_hex_byte al
debug_message ' ) '
mov CLK_time_min,ah ; Remainder is num. minutes
mov CLK_time_hour,al ; Quotient is num hours
;
; If everything worked correctly, the number of hours will not exceed 23.
; If there is a bug, it could be fixed by testing for 24 hours here, and
; setting the time to 23:59:59.99 instead.
;
; Finished, at last
;
debug_message '} '
ret
CLK_get_BIOS_time endp
resident_code ends
;***********************************************************************
;
; Look for a clock chip of whatever type.
; On entry, AL says what to do:
; 0: Don't bother doing anything.
; FFh or FEh: Look for all types of clock chip.
; other: ASCII character saying what type of chip to look for.
; On exit, Carry flag and AL say what was found:
; CF set: No clock chip
; CF clear, AL=ASCII character saying what type of chip was found.
; If a chip was found, other data (such as CLK_chip_IO_base) will also
; have been set, as required by that chip.
;
startup_code segment
CLK_find_chip proc near
or al, al
jz CLK_find_no_chip ; don't bother if zero
jl CLK_find_any_chip ; try all types if less than zero
push ax ; remember type for later
cmp al, '1' ; try MM58167AN ?
jne CLK_find_chip_2
call CLK_find_MM58167AN
jmp CLK_find_chip_pop_ax
CLK_find_chip_2:
cmp al, '2' ; try MSM6242 ?
jne CLK_find_no_chip
call CLK_find_MSM6242
;
; Here when chip might or might not have been found, and AX
; needs to be popped before returning to caller.
;
CLK_find_chip_pop_ax:
pop ax
ret
;
; Here to try all types of chip
;
CLK_find_any_chip:
call CLK_find_MM58167AN ; try MM58167AN
mov al, '1'
jnc CLK_find_chip_done ; yes ?
call CLK_find_MSM6242 ; try MSM6242
mov al, '2'
jnc CLK_find_chip_done ; yes?
;
; Here when chip was not found
;
CLK_find_no_chip:
stc
CLK_find_chip_done:
ret
CLK_find_chip endp
startup_code ends
;***********************************************************************
;
; Several display output functions intended mainly debugging. Some may also
; be used by the startup routine.
;
;
; The display_message function is used by the startup routine,
; so must be in the resident_code segment if debugging is enabled,
; or in the startup_code segment if debugging is disabled.
; The same applies to the display_char routine.
;
if do_debug_writes
resident_code segment
else
startup_code segment
endif ; do_debug_writes
;
; Display the single character in AL.
;
display_char proc near
;
; Just tell BIOS to display it, in the normal colour.
;
mov ah,0Eh
mov bx,1
int 10h
ret
display_char endp
;
; Display a message pointed to by DS:SI, with length in CX
;
display_message proc near
;
; Save registers
;
pushf
push ax
push bx
push cx
push si
;
; Loop displaying one byte at a time until count gets to zero
;
cld
display_msg_loop:
lodsb
call display_char
loop display_msg_loop
;
; Restore all modified registers, and return.
;
pop si
pop cx
pop bx
pop ax
popf
ret
display_message endp
if do_debug_writes
resident_code ends
else
startup_code ends
endif ; do_debug_writes
;
; All the remaining functions in this section are used for debugging only
;
if do_debug_writes
resident_code segment
;
; Display the number in AL as a single hexadecimal digit
;
display_hex_digit proc near
;
; Save registers
;
pushf
push ax
push bx
;
; Convert and display the digit
;
and al,0Fh
add al,'0'
cmp al,'9'
jbe display_hex_output
add al,'A'-0Ah-'0'
display_hex_output:
call display_char
;
; Restore registers and return
;
pop bx
pop ax
popf
ret
display_hex_digit endp
;
; Display a byte passed in AL, as two hexadecimal digits.
;
display_hex_byte proc near
;
; Save registers
;
pushf
push ax
push cx
;
; Isolate and display high digit
;
mov ah,al
mov cl,4
shr al,cl
call display_hex_digit
;
; Isolate and display low digit
;
mov al,ah
call display_hex_digit
;
; Restore registers and return
;
pop cx
pop ax
popf
ret
display_hex_byte endp
;
; Display several hex bytes, separated by blanks.
; Address passed in DS:SI, with count in CX.
;
display_hex_bytes proc near
;
; Save registers
;
pushf
push ax
push bx
push cx
push di
;
; Loop displaying bytes
;
cld
mov bx,1
display_bytes_loop:
lodsb
call display_hex_byte
mov al,' '
call display_char
loop display_bytes_loop
;
; Restore regs and return
;
pop di
pop cx
pop bx
pop ax
popf
ret
display_hex_bytes endp
resident_code ends
endif ; do_debug_writes
;***********************************************************************
;
; This is the main initialisation code for the CLOCK$ device.
;
; It prints the initialisation message, processes command line
; options (from the DEVICE=... line in the CONFIG.SYS file),
; installs pointers to suitable time get and set ruotines (based on the
; command line options and the presence or absence of suitable
; hardware), and finally returns a pointer to the end of the
; permanently resident memory.
;
; Because the total size of the resident code is small (about 1.5K),
; there is no great need to free the space used by routines that deal
; with timers that are not present. The code to handle a clock chip thus
; remains permanently resident even on a machine with no clock chip, etc.
; (Or, if you prefer, I am just too lazy to implement a method of dynamic
; relocation so that unused code and data can be freed.)
;
startup_data segment
;
; Various flags determined from the command line arguments and from
; tests done during the installation process.
; For most of these flags, the default value of 0FFh means the
; program must determine the correct value by performing tests
; during the initialisation. Values 0 and 1 mean respectively
; NO and YES. These values may be set by command line arguments or
; by tests done during the initialisation.
;
CLK_use_AT_BIOS db 0FFh ; Use AT-style real time clock BIOS support ?
CLK_use_chip db 0FFh ; Use add-on clock chip ?
; (Exception to normal meaning: 0FEh means
; command line said -C+ without saying
; what type of chip. Positive values are
; ASCII chars saying what type of chip
; will be used.)
CLK_use_BIOS db 0FFh ; Use normal BIOS tick counter functions ?
CLK_give_long_help db 0 ; Print long help message ?
CLK_were_errors db 0 ; Were there any errors ?
CLK_no_command_line db 1 ; Were there any command line options ?
;
; Messages displayed by the startup routine
;
illegal_opt_msg db ' Unrecognised option character ignored: "'
illegal_opt_char db 'X' ; Correct char gets inserted here
db '".'
db 13,10
illegal_opt_msg_len equ $ - illegal_opt_msg
short_help_msg db 'Use "DEVICE=CLOCK.DEV -H" for help.',13,10
short_help_msg_len equ $ - short_help_msg
long_help_msg label byte
db 'Use "DEVICE=CLOCK.DEV <options>" in the CONFIG.SYS file.',13,10
db 'Enable option X with "-X", "-X+", "/X" or "/X+"; disable with "-X-", "/X-".',13,10
db 'If option takes a value, use "-X=value" or "/X=value".',13,10
db 'Options are:',13,10
db ' -H Display this help message.',13,10
db ' (Use "-H-" to disable the short help message.)',13,10
db ' -A Use real-time clock support provided in AT-type BIOS.',13,10
db ' -B Use ordinary BIOS timer.',13,10
db ' -Ctype=addr Use clock chip at the specified hexadecimal '
db 'I/O address.',13,10
db ' Type is optional. Use 1 for MM58167AN chip, 2 for MSM6242 '
db 'chip.',13,10
db ' Addr is optional. If omitted, program will try to find '
db 'chip.',13,10
db 'Default: Program tries to use AT BIOS real time clock. Failing that, '
db 'it looks',13,10
db 'for clock chips in several standard locations. If that also '
db 'fails,',13,10
db 'it uses the ordinary BIOS timer (but fixes the MS-DOS 3.2 date '
db 'change bug).',13,10
db 'Note: If a clock chip is found, ordinary DOS date and time commands '
db 'will',13,10
db 'set the chip time. No need for the AT SETUP program or for the TIMER '
db 'or',13,10
db 'SETCLOCK and GETCLOCK programs supplied with add-on clock cards.',13,10
db 13,10
long_help_msg_len equ $ - long_help_msg
no_AT_BIOS_msg db ' AT-style clock BIOS support is unavailable.',13,10
no_AT_BIOS_msg_len equ $ - no_AT_BIOS_msg
no_chip_default_msg db ' Cannot find clock chip at standard address.',13,10
no_chip_default_msg_len equ $ - no_chip_default_msg
no_chip_specified_msg db ' Cannot find clock chip at specified address.',13,10
no_chip_specified_msg_len equ $ - no_chip_specified_msg
AT_BIOS_msg db ' DOS date and time operations will use AT BIOS '
db 'real time clock.',13,10
AT_BIOS_msg_len equ $ - AT_BIOS_msg
chip_msg db ' DOS date and time operations will use clock chip.'
db 13,10
chip_msg_len equ $ - chip_msg
BIOS_msg db ' DOS date and time operations will use the standard '
db 'BIOS timer.',13,10
BIOS_msg_len equ $ - BIOS_msg
time_now_msg label byte
db ' Current date and time: '
msg_weekday db 'Day '
msg_day db 'DD '
msg_month db 'Mmm '
msg_year db 'YYYY '
msg_hour db 'hh:'
msg_min db 'mm:'
msg_sec db 'ss'
db 13,10
time_now_msg_len equ $ - time_now_msg
month_names db 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec '
day_names db 'Sun Mon Tue Wed Thu Fri Sat '
startup_data ends
startup_code segment
CLK_init proc near
debug_message '{ init '
;
; Print an initialisation message
;
push cs ; Make DS:SI point to string
pop ds ;
mov si,offset cgroup:start_message
mov cx,start_message_len ; Put the length into CX
call display_message ; Display it
;
; Make DS:SI point to the command line arguments
;
lds bx,cs:[request_pointer] ; Point to request header
lds si,ds:[bx].D_INIT_args ; Point to command line
cld ; Make sure SI gets incremented
;
; The command line pointer points just after
; the equals sign of the DEVICE=... line in the CONFIG.SYS file.
;
; Skip any initial white space, and the driver name itself.
;
call opt_skip_space ; Ignore white space
call opt_ignore ; and driver name
;
; Loop processing option characters. Exit when end of line is reached.
;
CLK_init_opt_loop:
;
; Skip white space, slash and minus signs.
;
call opt_skip_space
call opt_skip_slash_minus
jcond jc,CLK_init_end_options ; Exit if end of line
;
; Remember that there were command line options.
;
mov cs:[CLK_no_command_line],0
;
; Get an option character. Carry flag will be set if there are no more
; options. Otherwise, option character will be in AL.
; The option character will be in upper-case.
;
call opt_get_uppercase_char
if do_debug_writes
jcond jc,CLK_init_end_options ; Exit if end of line
else
jc CLK_init_end_options ; Exit if end of line
endif
debug_message '( option char '
debug_show_char al
debug_message ' ) '
;
; Process the option character
;
cmp al,'H' ; Help
je CLK_init_opt_H
cmp al,'A' ; Use AT BIOS clock support
je CLK_init_opt_A
cmp al,'B' ; Use normal BIOS timer
je CLK_init_opt_B
cmp al,'C' ; Use clock chip
je CLK_init_opt_C
;;; cmp al,'P' ; Prompt user for initial time (not yet implemented)
;;; je CLK_init_opt_P
CLK_init_opt_illegal:
;
; The option character was illegal. Print a message and skip until
; white space or a slash.
;
push ds ; Save DS:SI
push si
push cs ; Make DS:SI point to message
pop ds
mov si,offset cgroup:illegal_opt_msg
mov cx,illegal_opt_msg_len ; Message length in CX
mov ds:[illegal_opt_char],al ; Shove the character into
; the message
call display_message
mov [CLK_were_errors],1 ; Remember there were errors
pop si ; Restore DS:SI
pop ds
call opt_ignore ; Skip until white space or slash
jmp CLK_init_opt_loop ; Process next character
CLK_init_opt_H:
;
; Check if it was H- or H+, and remember.
;
call opt_get_plus_minus ; Returns AL=1 for "+", 0 for "-"
mov cs:[CLK_give_long_help],al
jmp CLK_init_opt_loop ; Process next character
CLK_init_opt_A:
;
; Check if it was A- or A+, and remember.
;
call opt_get_plus_minus ; Returns AL=1 for "+", 0 for "-"
mov cs:[CLK_use_AT_BIOS],al
jmp CLK_init_opt_loop ; Process next character
CLK_init_opt_B:
;
; Check if it was B- or B+, and remember.
;
call opt_get_plus_minus ; Returns AL=1 for "+", 0 for "-"
mov cs:[CLK_use_BIOS],al
jmp CLK_init_opt_loop ; Process next character
CLK_init_opt_C:
;
; Start by assuming it was -C+
;
mov cs:[CLK_use_chip], 0FEh
;
; Check for a number immediately after the C (tells what type of chip).
; Note: this bypasses opt_get_char etc.
;
mov al, ds:[si] ; next char (just peek)
cmp al, '1' ; is it in ['1'..'2'] ?
jb CLK_init_opt_C_nonum ; no
cmp al, '2'
ja CLK_init_opt_C_nonum ; no
mov cs:[CLK_use_chip], al ; yes, remember it
inc si ; and skip past character
CLK_init_opt_C_nonum:
;
; Check if it was C- or C+ or C=
;
call opt_get_plus_minus_eq ; Returns AL=1 for "+", 0 for "-"
; AL = 2 for "="
or al, al ; Was it "C-" ?
jne CLK_init_opt_C_notminus
mov cs:[CLK_use_chip], al ; Yes, clear flag
jmp CLK_init_opt_loop ; and process nect option
CLK_init_opt_C_notminus:
cmp al,2 ; Was it "C=" ?
jcond jne, CLK_init_opt_loop ; No, process next option
CLK_init_opt_C_equals:
call opt_get_hex_word ; Yes, get the address
mov cs:[CLK_chip_IO_base],dx
jmp CLK_init_opt_loop ; Process next option
CLK_init_end_options:
;
; Finished reading the options from the command line.
; Now print help message if necessary.
;
push cs ; Make DS point to the right place
pop ds
test [CLK_give_long_help],0FFh ; Long help message ?
jz CLK_init_no_long_help
mov si, offset cgroup:long_help_msg
mov cx,long_help_msg_len
call display_message
jmp CLK_init_end_help
CLK_init_no_long_help:
mov al,[CLK_no_command_line] ; Short help message ?
or al,[CLK_were_errors] ;
jz CLK_init_end_help ;
mov si, offset cgroup:short_help_msg
mov cx,short_help_msg_len
call display_message
CLK_init_end_help:
;
; Check for AT BIOS clock support.
;
debug_message '{ test for AT BIOS : '
test [CLK_use_AT_BIOS],0FFh ; Don't even try if told not to
jz CLK_init_end_AT_BIOS ;
call CLK_find_AT_BIOS ; Try to use AT BIOS
jc CLK_init_no_AT_BIOS ; Jump if not found
mov [CLK_use_AT_BIOS],1 ; Remember it is available
jmp CLK_init_end_AT_BIOS ;
CLK_init_no_AT_BIOS:
;
; If user specified AT BIOS support but there is none, print error message.
;
mov al,[CLK_use_AT_BIOS] ; See what user asked for
mov [CLK_use_AT_BIOS],0 ; Remember not to use AT BIOS
test al,al ; Error if user said it
jl CLK_init_end_AT_BIOS ; ... was available
mov si, offset cgroup:no_AT_BIOS_msg
mov cx,no_AT_BIOS_msg_len
call display_message
CLK_init_end_AT_BIOS:
debug_hex_byte [CLK_use_AT_BIOS]
debug_message '} '
;
; Check for clock chip support.
;
debug_message '{ test for clock chip : '
mov al,[CLK_use_chip] ; Check what to do
or al, al ; Don't even try if told not to
jz CLK_init_end_chip
call CLK_find_chip ; Look for the clock chip
jc CLK_init_no_chip ; Jump if not found
mov [CLK_use_chip],al ; Remember it is available
jmp CLK_init_end_chip
CLK_init_no_chip:
;
; If user specified clock chip support but there is none, print error message.
;
mov al,[CLK_use_chip] ; See what user said
mov [CLK_use_chip],0 ; Remember not to use chip
cmp al, 0FFh ; FF means user said nothing
je CLK_init_end_chip ; so remain silent
test al,al ; FFFE means user said -C+
jl CLK_init_no_default_chip ; "no chip at default addr"
; else "no chip at spec addr"
mov si, offset cgroup:no_chip_specified_msg ; if "-C=xxxx"
mov cx,no_chip_specified_msg_len
call display_message
jmp short CLK_init_end_chip
CLK_init_no_default_chip:
mov si, offset cgroup:no_chip_default_msg ; if "-C+" but no chip
mov cx,no_chip_default_msg_len
call display_message
CLK_init_end_chip:
debug_hex_word [CLK_chip_IO_base]
debug_message '} '
;
; If neither AT BIOS nor clock chip support is available, use normal BIOS.
;
mov al,[CLK_use_AT_BIOS] ; Use AT BIOS ?
xor ah,ah
or ax,[CLK_chip_IO_base] ; Or use clock chip ?
jnz CLK_init_end_BIOS ; OK if one or the other
debug_message '{ Must use normal BIOS } '
mov [CLK_use_BIOS],1 ; Remember to use normal BIOS
CLK_init_end_BIOS:
;
; Store pointers to suitable clock get and set procedures into the
; area used by CLK_fix_our_time and CLK_set_other_timers.
;
; If there is AT BIOS support then
; Use AT BIOS
; Else If clock chip support then
; Use clock chip, but make plain BIOS time match chip time
; Else (must be just plain BIOS support)
; Use only plain BIOS
;
; Instal AT BIOS access procedures.
;
test [CLK_use_AT_BIOS],0FFh ; Use AT BIOS ?
jz CLK_init_store_no_AT_BIOS
; Yes, use AT BIOS
debug_message '( Installing AT BIOS procedures ) '
mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_or_other_time
mov [CLK_get_other_procedure_ptr], offset cgroup:CLK_get_AT_BIOS_time
mov [CLK_set_procedure_ptr], offset cgroup:CLK_set_AT_BIOS_time
mov si,offset cgroup:AT_BIOS_msg ; Print message
mov cx,AT_BIOS_msg_len
call display_message
jmp CLK_init_store_end
CLK_init_store_no_AT_BIOS:
;
; Instal clock chip access procedures.
;
test [CLK_use_chip],0FFh ; Use clock chip ?
jz CLK_init_store_no_chip
; Yes, use chip
debug_message '( Installing clock chip [type '
debug_hex_byte [CLK_use_chip]
debug_message '] procedures ) '
mov si,offset cgroup:chip_msg ; Print message
mov cx,chip_msg_len
call display_message
cmp [CLK_use_chip],'1' ; MM58167AN chip?
je CLK_init_store_MM58167AN
cmp [CLK_use_chip],'2' ; MSM6242 chip?
je CLK_init_store_MSM6242
; should never get here, but just in case...
debug_message '*** Internal error : Invalid chip type *** '
jmp CLK_init_store_no_chip ; must have been mistaken!
CLK_init_store_MM58167AN:
mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_MM58167AN_time
mov [CLK_set_procedure_ptr], offset cgroup:CLK_set_MM58167AN_time
jmp CLK_init_store_end
CLK_init_store_MSM6242:
mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_or_other_time
mov [CLK_get_other_procedure_ptr], offset cgroup:CLK_get_MSM6242_time
mov [CLK_set_procedure_ptr], offset cgroup:CLK_set_MSM6242_time
jmp CLK_init_store_end
CLK_init_store_no_chip:
;
; Instal plain BIOS access procedures.
;
debug_message '( Installing plain BIOS procedures ) '
mov si,offset cgroup:BIOS_msg ; Print message
mov cx,BIOS_msg_len
call display_message
mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_time
mov [CLK_set_procedure_ptr], offset cgroup:null_proc
CLK_init_store_end:
;
; Get the current date and time.
; Ensure that year, month, day and weekday are known.
;
call CLK_fix_our_time ; Get the time from somewhere
call CLK_set_BIOS_time ; Make plain BIOS time match it
; (no harm done if time came from BIOS)
call CLK_conv_days_ccyymmdd ; Convert to year, month, day
call CLK_conv_days_weekday ; Find the day of the week
;
; Format the date and time for printing
;
mov al,[CLK_date_century] ; Century
call conv_binary_ASCII_decimal
mov word ptr [msg_year],ax
mov al,[CLK_date_year] ; Year
call conv_binary_ASCII_decimal
mov word ptr [msg_year+2],ax
mov bl,[CLK_date_month] ; Month
dec bl ; Subtract 1 and multiply by 4
shl bl,1 ; to get offset into table
shl bl,1
xor bh,bh
mov ax, word ptr [month_names][bx] ; Copy 4 bytes
mov word ptr [msg_month],ax
mov ax, word ptr [month_names][bx+2]
mov word ptr [msg_month+2],ax
mov al,[CLK_date_day] ; Day of month
call conv_binary_ASCII_decimal
mov word ptr [msg_day],ax
mov bl,[CLK_date_weekday] ; Day of week
shl bl,1 ; Multiply by 4
shl bl,1 ; to get offset into table
mov ax, word ptr [day_names][bx] ; Copy 4 bytes
mov word ptr [msg_weekday],ax
mov ax,word ptr [day_names][bx+2]
mov word ptr [msg_weekday+2],ax
mov al,[CLK_time_hour] ; Hour
call conv_binary_ASCII_decimal
mov word ptr [msg_hour],ax
mov al,[CLK_time_min] ; Minute
call conv_binary_ASCII_decimal
mov word ptr [msg_min],ax
mov al,[CLK_time_sec] ; Second
call conv_binary_ASCII_decimal
mov word ptr [msg_sec],ax
;
; Print the date and time.
;
mov si,offset cgroup:time_now_msg
mov cx,time_now_msg_len
call display_message
;
; Return address of end of resident memory.
; Note: The IBM manual says it is the address of the first byte to be freed,
; while the Microsoft manual says it is the address of the last byte to be
; retained. Play safe and return the address of the first byte to be freed.
;
mov ax, offset cgroup:end_resident_memory
; This is the first byte to be freed
lds bx,cs:[request_pointer] ; Make DS:BX point to request
mov word ptr ds:[bx].D_INIT_end, ax ; Return offset
mov ax,cs ; Also return segment
mov word ptr ds:[bx].D_INIT_end[2], ax
jmp exit_ok
CLK_init endp
startup_code ends
;***********************************************************************
;
; Convert a byte to two ASCII decimal digits.
; The input binary calue (in AL) must be less than 100.
; The two bytes will be in AH (least significant) and AL (most significant).
; The bytes are in this order to allow a "MOV [memory],AX" instruction
; to put the high digit first.
;
resident_code segment
conv_binary_ASCII_decimal proc near
aam ; AH will be high digit
; AL will be low digit
add ax,'00' ; Convert to ASCII digits
xchg ah,al ; Swap the order
ret
conv_binary_ASCII_decimal endp
resident_code ends
;***********************************************************************
;
; Some subroutines concerned with command line option processing.
; Called with DS:SI pointing to the next character in the command line.
; They exit with the carry flag set when the end of the option line
; is reached.
;
startup_code segment
;
; Get a single character
;
opt_get_char proc near
;
; Get character
;
lodsb ; Get a character
cmp al,13 ; Is it the end of the line ?
je opt_get_char_end_line
cmp al,10
je opt_get_char_end_line
;
; Exit with carry clear if not end of line.
;
debug_message '( get_opt_char "'
debug_show_char al
debug_message '" hex '
debug_hex_byte al
debug_message ' ) '
clc
ret
;
; Decrement the pointer and exit with carry flag set if end of line
;
opt_get_char_end_line:
debug_message '( get_opt_char end of line ) '
dec si ; Undo the pointer increment
stc
ret
opt_get_char endp
;
; Get a single character, and convert it to upper-case.
;
opt_get_uppercase_char proc near
;
; Get char and exit if end of line
;
call opt_get_char
jc opt_uppercase_end
;
; Convert to uppercase and clear carry flag
;
cmp al,'a'
jb opt_uppercase_OK ; It was not a lowercase char
cmp al,'z'
ja opt_uppercase_OK ; It was not a lowercase char
add al,'A'-'a' ; Convert it to uppercase
opt_uppercase_OK:
clc
opt_uppercase_end:
ret
opt_get_uppercase_char endp
;
; Skip leading white space.
;
opt_skip_space proc near
;
; Get char and exit if end of line.
; Loop if it is a white space character.
; Exit with pointer at next non-white space character.
;
; Note: a NUL is treated as a white space character. This is because MS-DOS
; version 2 puts a NUL after the driver file name in the command line it
; passes to the device driver initialisation routine.
;
opt_skip_space_loop:
call opt_get_char
jc opt_skip_end
cmp al,' ' ; Is it a space ?
je opt_skip_space_loop
cmp al,9 ; Is it a TAB ?
je opt_skip_space_loop
cmp al,0 ; What about a NUL ?
je opt_skip_space_loop
;
; Decrement pointer and exit with carry clear if all is OK
;
dec si
clc
opt_skip_end:
ret
opt_skip_space endp
;
; Skip slash or minus sign before option character.
;
opt_skip_slash_minus proc near
;
; Get char and exit if end of line.
; Un-get the char if not a slash or minus sign.
;
call opt_get_char
jc opt_skip_slash_end
cmp al,'/' ; A slash ?
je opt_skip_slash_OK
cmp al,'-' ; Or a dash ?
je opt_skip_slash_OK
;
; Decrement pointer and exit with carry clear if not a slash or dash
;
dec si
opt_skip_slash_OK:
clc
opt_skip_slash_end:
ret
opt_skip_slash_minus endp
;
; Ignore everything until next white space character.
;
opt_ignore proc near
;
; Get char and exit if end of line.
; Exit if it is a white space character; else loop.
;
; Note: a NUL is treated as a white space character. This is because MS-DOS
; version 2 puts a NUL after the driver file name in the command line it
; passes to the device driver initialisation routine.
;
opt_ignore_loop:
call opt_get_char
jc opt_ignore_end
cmp al,' ' ; Is it a space ?
je opt_ignore_OK
cmp al,9 ; Is it a TAB ?
je opt_ignore_OK
cmp al,0 ; What about a NUL ?
je opt_ignore_OK
jmp opt_ignore_loop ; Loop for next char
;
; Decrement pointer and exit with carry clear if all is OK
;
opt_ignore_OK:
dec si
clc
opt_ignore_end:
ret
opt_ignore endp
;
; Get plus or minus sign (after an option character).
; Behave as if a plus sign was present if there was no plus or minus.
; Return AL=1 for plus, AL=0 for minus.
;
opt_get_plus_minus proc near
;
; Get char and exit if end of line
;
call opt_get_char
mov ah,al ; Save char
mov al,1 ; Assume "+"
jc opt_plus_minus_end ; Exit if end of line
;
; Check for plus or minus sign. Decrement pointer if neither.
;
xor al,al ; Ready in case it is a '-'
cmp ah,'-'
je opt_plus_minus_OK
mov al,1 ; Ready in case it is a '+'
cmp ah,'+'
je opt_plus_minus_OK
dec si ; Decrement pointer if neither
opt_plus_minus_OK:
clc
opt_plus_minus_end:
ret
opt_get_plus_minus endp
;
; Get plus, minus or equals sign (after an option character).
; Behave as if a plus sign was present if there was nothing.
; Return AL=1 for plus, AL=0 for minus, AL=2 for equals.
;
opt_get_plus_minus_eq proc near
;
; Get char and exit if end of line
;
call opt_get_char
mov ah,al ; Save char
mov al,1 ; Assume "+"
jc opt_plus_minus_eq_end ; Exit if end of line
;
; Check for plus or minus sign. Decrement pointer if neither.
;
xor al,al ; Ready in case it is a '-'
cmp ah,'-'
je opt_plus_minus_eq_OK
mov al,2 ; Ready in case it is an '='
cmp ah,'='
je opt_plus_minus_eq_OK
mov al,1 ; Ready in case it is a '+'
cmp ah,'+'
je opt_plus_minus_eq_OK
dec si ; Decrement pointer if neither
opt_plus_minus_eq_OK:
clc
opt_plus_minus_eq_end:
ret
opt_get_plus_minus_eq endp
;
; Get a hexadecimal digit.
; Exit with carry flag set if end of line,
; or with zero flag set if no more hexadecimal digits,
; or with value in AL if valid digit found.
;
opt_get_hex_digit proc near
;
; Get char and exit if end of line
;
call opt_get_uppercase_char
jc opt_hex_digit_end
;
; Check for hex digit.
;
sub al,'0'
jl opt_hex_digit_bad
cmp al,9
jle opt_hex_digit_OK
sub al,'A'-'0'
jl opt_hex_digit_bad
add al,0Ah
cmp al,0Fh
jle opt_hex_digit_OK
;
; Decrement pointer and exit with carry clear and zero flag set for bad digit.
;
opt_hex_digit_bad:
dec si
test al,0 ; Set zero flag and clear carry
opt_hex_digit_end:
ret
;
; Exit with carry and zero clear if no problem
;
opt_hex_digit_OK:
cmp al,0FFh ; Clear the zero flag
clc ; Clear the carry flag
ret
opt_get_hex_digit endp
;
; Get hexadecimal word (after an option character).
; Value is returned in DX.
;
opt_get_hex_word proc near
;
; Start with a value of zero.
; Loop until a non-hex character is found.
;
xor dx,dx
opt_hex_word_loop:
;
; Get a character and exit if end of line or if no more digits.
;
call opt_get_hex_digit
jc opt_hex_word_end ; End of line if carry flag set
jz opt_hex_word_OK ; No more digits if zero flag set
;
; Shift previous result, add new digit, and loop for next digit.
;
mov cl,4
shl dx,cl
xor ah,ah
add dx,ax
jmp opt_hex_word_loop
;
; Exit with no carry if completed successfully.
;
opt_hex_word_OK:
clc
opt_hex_word_end:
ret
opt_get_hex_word endp
startup_code ends
;***********************************************************************
if add_test_code
;
; This code is intended to make debugging easier.
; If the program is compiled with the add_test_code option, the resulting
; .EXE program will not be convertible by EXE2BIN, but can be run as an
; ordinary program. It will pass its command line arguments to the
; device driver initialisation routine, and will also perform a device
; read and write operation, before terminating.
; If the program is run under the control of a debugger, passing suitable
; arguments to the device driver can be accomplished by modifying the
; data in the init_request, read_request and write_request buffers.
;
startup_data segment
;
; The command line options passed to the initialisation routine
;
command_line db 128 dup (13)
;
; The date and time, in the format used by the device driver read and write
; operations.
;
buffer db 6 dup (?)
;
; A properly formatted initialisation request.
;
init_request label byte
init_length db 23
init_unit db 0
init_command db 0 ; INIT
init_status dw ?
init_reserv db 8 dup (0)
init_units db 0
init_end dd ?
init_bpb dd cgroup:command_line
init_blocknum db 0
;
; A properly formatted read request.
;
read_request label byte
read_length db 26
read_unit db 0
read_command db 4 ; READ
read_status dw ?
read_reserv db 8 dup (0)
read_media db 0
read_address dd cgroup:buffer
read_datalen dw 6
read_start dw 0
read_volume dd ?
;
; A properly formatted write request.
;
write_request label byte
write_length db 26
write_unit db 0
write_command db 8 ; WRITE
write_status dw ?
write_reserv db 8 dup (0)
write_media db 0
write_address dd cgroup:buffer
write_datalen dw 6
write_start dw 0
write_volume dd ?
startup_data ends
startup_code segment
;
; Main entry point for testing.
;
test_proc proc near
test_start:
;
; Copy command line parameters to buffer area.
;
mov si,0081h
push cs
pop es
mov di,offset cgroup:command_line
cld
mov cx,127
rep movsb
test_init:
;
; Initialise.
;
mov bx,offset cgroup:init_request
call test_call
test_read:
;
; Read.
;
mov bx,offset cgroup:read_request
call test_call
test_write:
;
; Write.
;
mov bx,offset cgroup:write_request
call test_call
test_exit:
;
; Exit.
;
mov ax,4C00h
int 21h
test_proc endp
test_call proc near
push cs ; Make ES:BX point to request area
pop es
nop
call CLK_strategy ; Call strategy and interrupt
call CLK_interrupt
ret
test_call endp
startup_code ends
endif ; add_test_code
;***********************************************************************
; Ugly, isn't it?
if add_test_code
end_statement equ <end cgroup:test_start>
else
end_statement equ <end>
endif
end_statement